{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "运行环境：\n",
    "python              3.6\n",
    "torch               0.4.1\n",
    "torchvision         0.2.1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "## 6.1.1 Tensor的数据类型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[ 0.0000e+00,  0.0000e+00,  9.2070e-29],\n",
      "        [-1.5846e+29,  4.2981e+21,  6.3828e+28]])\n",
      "tensor([2., 3., 4., 5.])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "# torch.FloatTensor:用于生成数据类型为浮点型的Tensor\n",
    "a = torch.FloatTensor(2,3)  # 参数：维度值\n",
    "b = torch.FloatTensor([2,3,4,5]) # 参数：列表\n",
    "print(a)\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[          0,           0,   283710376],\n",
      "        [-1610612736,   245891074,           1]], dtype=torch.int32)\n",
      "tensor([2, 3, 4, 5], dtype=torch.int32)\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "# torch.IntTensor:用于生成数据类型为整型的Tensor\n",
    "a = torch.IntTensor(2,3) # 参数：维度值\n",
    "b = torch.IntTensor([2,3,4,5]) # 参数：列表\n",
    "print(a)\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[0.6302, 0.9297, 0.0518],\n",
      "        [0.4095, 0.7822, 0.5054]])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "# torch.rand:随机生成的浮点数据在0~1区间均匀分布\n",
    "a = torch.rand(2,3)  \n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-0.7373, -0.3723, -0.4248],\n",
      "        [ 0.0408, -1.4669,  0.1767]])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "# torch.randn: 随机生成的浮点数的取值满足均值为0、方差为1的正太分布\n",
    "a = torch.randn(2,3)\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18,\n",
      "        19])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "a = torch.arange(1,20,1) #[start,step,end)\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[0., 0., 0.],\n",
      "        [0., 0., 0.]])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "a = torch.zeros(2,3)\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.1.2 Tensor的运算"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-0.7441, -0.4257,  2.0181],\n",
      "        [-0.9058, -0.9558,  2.4911]])\n",
      "tensor([[0.7441, 0.4257, 2.0181],\n",
      "        [0.9058, 0.9558, 2.4911]])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "a = torch.randn(2,3)\n",
    "print(a)\n",
    "\n",
    "b = torch.abs(a)\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-1.4264, -1.8632,  1.7996],\n",
      "        [-0.8573, -0.3676, -1.0642]])\n",
      "tensor([[-0.0947, -1.0489, -0.0218],\n",
      "        [ 0.2578,  1.7863, -0.5491]])\n",
      "tensor([[-1.5210, -2.9121,  1.7779],\n",
      "        [-0.5995,  1.4187, -1.6133]])\n",
      "tensor([[ 0.2704,  0.1071,  1.4802],\n",
      "        [-0.8565, -0.3317, -0.8125]])\n",
      "tensor([[10.2704, 10.1071, 11.4802],\n",
      "        [ 9.1435,  9.6683,  9.1875]])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "a = torch.randn(2,3)\n",
    "print(a)\n",
    "\n",
    "b = torch.randn(2,3)\n",
    "print(b)\n",
    "\n",
    "c = torch.add(a,b)\n",
    "print(c)\n",
    "\n",
    "d = torch.randn(2,3)\n",
    "print(d)\n",
    "\n",
    "e = torch.add(d,10)\n",
    "print(e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[ 1.6828, -0.8255,  0.4306],\n",
      "        [ 0.0847, -0.3267, -0.8183]])\n",
      "tensor([[ 0.1000, -0.1000,  0.1000],\n",
      "        [ 0.0847, -0.1000, -0.1000]])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "a = torch.randn(2,3)\n",
    "print(a)\n",
    " \n",
    "b = torch.clamp(a, -0.1, 0.1)   # 裁剪\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-0.4731,  1.2993,  0.6351],\n",
      "        [-0.6879,  1.7263, -1.0909]])\n",
      "tensor([[ 0.1953, -1.7439,  0.3514],\n",
      "        [-1.6562, -0.6729, -0.6220]])\n",
      "tensor([[-2.4226, -0.7451,  1.8073],\n",
      "        [ 0.4153, -2.5655,  1.7540]])\n",
      "tensor([[ 1.1907, -0.0283,  1.8689],\n",
      "        [-0.1884,  1.4081,  0.2591]])\n",
      "tensor([[ 0.1191, -0.0028,  0.1869],\n",
      "        [-0.0188,  0.1408,  0.0259]])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "a = torch.randn(2,3)\n",
    "print(a)\n",
    "\n",
    "b = torch.randn(2,3)\n",
    "print(b)\n",
    "\n",
    "c = torch.div(a,b)  \n",
    "print(c)\n",
    "\n",
    "d = torch.randn(2,3)\n",
    "print(d)\n",
    "\n",
    "e = torch.div(d,10)\n",
    "print(e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[ 0.8339,  0.0305, -0.8853],\n",
      "        [ 1.4199,  2.1894, -0.7583]])\n",
      "tensor([[-1.6293,  0.1275, -0.3693],\n",
      "        [ 0.5709,  0.2411, -0.0157]])\n",
      "tensor([[-1.3587,  0.0039,  0.3270],\n",
      "        [ 0.8107,  0.5279,  0.0119]])\n",
      "tensor([[ 0.0512,  0.1854, -0.5609],\n",
      "        [ 0.1652,  0.5285,  0.4794]])\n",
      "tensor([[ 0.5125,  1.8544, -5.6094],\n",
      "        [ 1.6519,  5.2851,  4.7944]])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "a = torch.randn(2,3)\n",
    "print(a)\n",
    "\n",
    "b = torch.randn(2,3)\n",
    "print(b)\n",
    "\n",
    "c = torch.mul(a,b)\n",
    "print(c)\n",
    "\n",
    "d = torch.randn(2,3)\n",
    "print(d)\n",
    "\n",
    "e = torch.mul(d,10)\n",
    "print(e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-0.0143,  0.3575,  1.3603],\n",
      "        [-0.8925, -1.7116,  0.0544]])\n",
      "tensor([[0.0002, 0.1278, 1.8503],\n",
      "        [0.7965, 2.9297, 0.0030]])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "a = torch.randn(2,3)\n",
    "print(a)\n",
    "\n",
    "b = torch.pow(a, 2) # 乘方\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-0.5551, -0.8693, -1.0027],\n",
      "        [-0.9902,  1.0206,  0.6531]])\n",
      "tensor([[-1.8841,  1.1860],\n",
      "        [-0.1898,  2.3994],\n",
      "        [ 1.4658, -1.4973]])\n",
      "tensor([[-0.2590, -1.2428],\n",
      "        [ 2.6293,  0.2966]])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "a = torch.randn(2,3)\n",
    "print(a)\n",
    "\n",
    "b = torch.randn(3, 2)\n",
    "print(b)\n",
    "c = torch.mm(a, b) #矩阵乘法\n",
    "print(c)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-0.4150, -0.1400, -0.4785],\n",
      "        [ 0.4184, -0.2565,  0.0634]])\n",
      "tensor([ 1.6236, -2.1173,  2.4018])\n",
      "tensor([-1.5266,  1.3746])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "a = torch.randn(2,3)\n",
    "print(a)\n",
    "\n",
    "b = torch.randn(3)\n",
    "print(b)\n",
    "\n",
    "c = torch.mv(a, b)  # 矩阵*向量\n",
    "print(c)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.1.3 搭建一个简易神经网络\n",
    "使用前向传播和后向传播实现了对这个模型的训练和对权重参数的优化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch:0, Loss:75053984.0000\n",
      "Epoch:1, Loss:57412708.0000\n",
      "Epoch:2, Loss:45503956.0000\n",
      "Epoch:3, Loss:37028808.0000\n",
      "Epoch:4, Loss:30875136.0000\n",
      "Epoch:5, Loss:26159328.0000\n",
      "Epoch:6, Loss:22446068.0000\n",
      "Epoch:7, Loss:19466286.0000\n",
      "Epoch:8, Loss:17085500.0000\n",
      "Epoch:9, Loss:15131197.0000\n",
      "Epoch:10, Loss:13506137.0000\n",
      "Epoch:11, Loss:12142673.0000\n",
      "Epoch:12, Loss:10983839.0000\n",
      "Epoch:13, Loss:9988542.0000\n",
      "Epoch:14, Loss:9131620.0000\n",
      "Epoch:15, Loss:8391284.0000\n",
      "Epoch:16, Loss:7744724.0000\n",
      "Epoch:17, Loss:7173821.5000\n",
      "Epoch:18, Loss:6665968.5000\n",
      "Epoch:19, Loss:6212915.5000\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "batch_n = 100 # batch_n是在一个批次中输入数据的数量\n",
    "hidden_layer = 100 # hidden_layer定义经过隐藏层后保留的数据特征的个数\n",
    "input_data = 1000 # 输入数据包含的数据特征有input_data个\n",
    "output_data = 10 # 输出out_put个分类结果值\n",
    "\n",
    "# [100,1000] --[1000,100]->> [100,100] --[100,10]->> [100,10]\n",
    "x = torch.randn(batch_n, input_data) # x: 100*1000\n",
    "y = torch.randn(batch_n, output_data) # y: 100*10\n",
    "\n",
    "w1 = torch.randn(input_data, hidden_layer) # w1: 1000*100\n",
    "w2 = torch.randn(hidden_layer, output_data) # w2:100*10\n",
    "\n",
    "epoch_n = 20   # 训练次数\n",
    "learning_rate = 1e-6  # 学习率\n",
    "\n",
    "for epoch in range(epoch_n):\n",
    "    h1 = x.mm(w1) #100*100  \n",
    "    h1 = h1.clamp(min = 0)  # ReLU activation Function\n",
    "    y_pred = h1.mm(w2) #100*10  前向传播的预测结果\n",
    "    \n",
    "    loss = (y_pred - y).pow(2).sum()   # 均方误差函数计算loss\n",
    "    print(\"Epoch:{}, Loss:{:.4f}\".format(epoch,loss))  # 可由输出观察到 Loss值逐渐减小\n",
    "    \n",
    "    '''\n",
    "    链式求导得w1、w2的梯度 grad_w1、grad_w2\n",
    "    '''\n",
    "    grad_y_pred = 2*(y_pred - y) # 100*10\n",
    "    grad_w2 = h1.t().mm(grad_y_pred) # 100*10\n",
    "    \n",
    "    grad_h = grad_y_pred.clone() # 100*10\n",
    "    grad_h = grad_h.mm(w2.t()) # 100*100 = 100*10 × 10*100\n",
    "    grad_h.clamp_(min=0) \n",
    "    \n",
    "    grad_w1 = x.t().mm(grad_h) # 1000*10 = 1000*100 × 100*10\n",
    "    w1 -= learning_rate*grad_w1 # 1000*10\n",
    "w2 -= learning_rate*grad_w2 # 100*10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.2.1 torch.autograd和Variable\n",
    "- torch.autograd包可以使模型参数`自动计算`在优化过程中需要用到的梯度值，降低了实现后向传播代码的复杂度"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch:0, Loss:41120620.0000\n",
      "Epoch:1, Loss:72134664.0000\n",
      "Epoch:2, Loss:311747168.0000\n",
      "Epoch:3, Loss:944317568.0000\n",
      "Epoch:4, Loss:167491360.0000\n",
      "Epoch:5, Loss:26751306.0000\n",
      "Epoch:6, Loss:15471005.0000\n",
      "Epoch:7, Loss:10613440.0000\n",
      "Epoch:8, Loss:7897940.0000\n",
      "Epoch:9, Loss:6176373.5000\n",
      "Epoch:10, Loss:4998452.0000\n",
      "Epoch:11, Loss:4155680.0000\n",
      "Epoch:12, Loss:3526302.5000\n",
      "Epoch:13, Loss:3040388.2500\n",
      "Epoch:14, Loss:2655105.2500\n",
      "Epoch:15, Loss:2342884.2500\n",
      "Epoch:16, Loss:2085955.0000\n",
      "Epoch:17, Loss:1870547.5000\n",
      "Epoch:18, Loss:1687957.1250\n",
      "Epoch:19, Loss:1531513.3750\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch.autograd import Variable\n",
    "batch_n = 100\n",
    "hidden_layer = 100\n",
    "input_data = 1000\n",
    "output_data = 10\n",
    "\n",
    "x = Variable(torch.randn(batch_n, input_data), requires_grad = False)\n",
    "y = Variable(torch.randn(batch_n, output_data), requires_grad = False)\n",
    "\n",
    "w1 = Variable(torch.randn(input_data, hidden_layer), requires_grad = True)\n",
    "w2 = Variable(torch.randn(hidden_layer, output_data), requires_grad = True)\n",
    "\n",
    "epoch_n = 20\n",
    "learning_rate = 1e-6\n",
    "\n",
    "for epoch in range(epoch_n):\n",
    "    y_pred = x.mm(w1).clamp(min = 0).mm(w2)\n",
    "    loss = (y_pred - y).pow(2).sum()\n",
    "    print(\"Epoch:{}, Loss:{:.4f}\".format(epoch,loss))\n",
    "    \n",
    "    loss.backward()  #让模型根据计算图自动计算每个节点的梯度值并根据需求进行保留\n",
    "    \n",
    "    w1.data -= learning_rate*w1.grad.data  \n",
    "    w2.data -= learning_rate*w2.grad.data\n",
    "    \n",
    "    w1.grad.data.zero_()  # 置零\n",
    "    w2.grad.data.zero_()  # 若不置零，则计算的梯度值会被一直累加"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.2.2 自定义传播函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch:0, Loss:36254352.0000\n",
      "Epoch:1, Loss:34979972.0000\n",
      "Epoch:2, Loss:34895552.0000\n",
      "Epoch:3, Loss:30237686.0000\n",
      "Epoch:4, Loss:21398412.0000\n",
      "Epoch:5, Loss:12290640.0000\n",
      "Epoch:6, Loss:6378899.5000\n",
      "Epoch:7, Loss:3365562.7500\n",
      "Epoch:8, Loss:1996945.7500\n",
      "Epoch:9, Loss:1354266.5000\n",
      "Epoch:10, Loss:1018180.8125\n",
      "Epoch:11, Loss:814492.2500\n",
      "Epoch:12, Loss:674306.1250\n",
      "Epoch:13, Loss:568980.6250\n",
      "Epoch:14, Loss:485897.6875\n",
      "Epoch:15, Loss:418412.8438\n",
      "Epoch:16, Loss:362563.5938\n",
      "Epoch:17, Loss:315860.3438\n",
      "Epoch:18, Loss:276484.2812\n",
      "Epoch:19, Loss:243105.0625\n",
      "Epoch:20, Loss:214615.3281\n",
      "Epoch:21, Loss:190178.6719\n",
      "Epoch:22, Loss:169150.8750\n",
      "Epoch:23, Loss:150961.2344\n",
      "Epoch:24, Loss:135153.0312\n",
      "Epoch:25, Loss:121354.5938\n",
      "Epoch:26, Loss:109286.4922\n",
      "Epoch:27, Loss:98667.2969\n",
      "Epoch:28, Loss:89288.0156\n",
      "Epoch:29, Loss:80978.7500\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch.autograd import Variable\n",
    "batch_n = 64\n",
    "hidden_layer = 100\n",
    "input_data = 1000\n",
    "output_data = 10\n",
    "\n",
    "class Model(torch.nn.Module):\n",
    "\n",
    "    def __init__(self):\n",
    "        super(Model, self).__init__()\n",
    "    \n",
    "    def forward(self, input, w1, w2):\n",
    "        x = torch.mm(input, w1)\n",
    "        x = torch.clamp(x, min = 0)\n",
    "        x =torch.mm(x, w2)\n",
    "        return x\n",
    "    \n",
    "    def backward(self):\n",
    "        pass\n",
    "    \n",
    "model = Model()\n",
    "\n",
    "\n",
    "x = Variable(torch.randn(batch_n, input_data), requires_grad = False)\n",
    "y = Variable(torch.randn(batch_n, output_data), requires_grad = False)\n",
    "\n",
    "w1 = Variable(torch.randn(input_data, hidden_layer), requires_grad = True)\n",
    "w2 = Variable(torch.randn(hidden_layer, output_data) ,requires_grad = True)\n",
    "\n",
    "epoch_n = 30\n",
    "learning_rate = 1e-6\n",
    "\n",
    "for epoch in range(epoch_n):\n",
    "    y_pred = model(x, w1, w2)\n",
    "    \n",
    "    loss = (y_pred - y).pow(2).sum()\n",
    "    print(\"Epoch:{}, Loss:{:.4f}\".format(epoch,loss))\n",
    "    loss.backward()\n",
    "    \n",
    "    w1.data -= learning_rate * w1.grad.data\n",
    "    w2.data -= learning_rate * w2.grad.data\n",
    "    \n",
    "    w1.grad.data.zero_()\n",
    "    w2.grad.data.zero_()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.3.1 PyTorch之torch.nn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sequential(\n",
      "  (0): Linear(in_features=1000, out_features=100, bias=True)\n",
      "  (1): ReLU()\n",
      "  (2): Linear(in_features=100, out_features=10, bias=True)\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "hidden_layer = 100\n",
    "input_data = 1000\n",
    "output_data = 10\n",
    "models = torch.nn.Sequential(  # 序列容器\n",
    "torch.nn.Linear(input_data, hidden_layer),  # 从输入层到隐藏层的线性变换\n",
    "torch.nn.ReLU(),  # 激活函数\n",
    "torch.nn.Linear(hidden_layer, output_data) # 从隐藏层到输出层的线性变换\n",
    ")\n",
    "print(models)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sequential(\n",
      "  (Line1): Linear(in_features=1000, out_features=100, bias=True)\n",
      "  (Relu1): ReLU()\n",
      "  (Line2): Linear(in_features=100, out_features=10, bias=True)\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "hidden_layer = 100\n",
    "input_data = 1000\n",
    "output_data = 10\n",
    "from collections import OrderedDict  # 有序字典\n",
    "models = torch.nn.Sequential(OrderedDict([  \n",
    "(\"Line1\",torch.nn.Linear(input_data, hidden_layer)),\n",
    "(\"Relu1\",torch.nn.ReLU()),\n",
    "(\"Line2\",torch.nn.Linear(hidden_layer, output_data))])\n",
    ")\n",
    "print(models)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor(1.9899)\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch.autograd import Variable\n",
    "loss_f = torch.nn.MSELoss()  #定义loss_f # 均方误差函数类MSELoss计算损失值\n",
    "x = Variable(torch.randn(100,100))\n",
    "y = Variable(torch.randn(100,100))\n",
    "loss = loss_f(x,y) #x、yd的维度需要一致\n",
    "print(loss.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor(1.1358)\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch.autograd import Variable\n",
    "loss_f = torch.nn.L1Loss()  # 平均绝对误差函数\n",
    "x = Variable(torch.randn(100,100))\n",
    "y = Variable(torch.randn(100,100))\n",
    "loss = loss_f(x,y)\n",
    "print(loss.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor(1.6661)\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch.autograd import Variable\n",
    "loss_f = torch.nn.CrossEntropyLoss()  # 交叉熵\n",
    "x = Variable(torch.randn(3, 5))\n",
    "y = Variable(torch.LongTensor(3).random_(5))\n",
    "loss = loss_f(x,y)\n",
    "print(loss.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch:0, Loss:1.0786\n",
      "Epoch:1000, Loss:0.9990\n",
      "Epoch:2000, Loss:0.9299\n",
      "Epoch:3000, Loss:0.8691\n",
      "Epoch:4000, Loss:0.8151\n",
      "Epoch:5000, Loss:0.7665\n",
      "Epoch:6000, Loss:0.7222\n",
      "Epoch:7000, Loss:0.6814\n",
      "Epoch:8000, Loss:0.6431\n",
      "Epoch:9000, Loss:0.6072\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch.autograd import Variable\n",
    "batch_n = 100\n",
    "hidden_layer = 100\n",
    "input_data = 1000\n",
    "output_data = 10\n",
    "x = Variable(torch.randn(batch_n, input_data), requires_grad = False)\n",
    "y = Variable(torch.randn(batch_n, output_data), requires_grad = False)\n",
    "\n",
    "models = torch.nn.Sequential(\n",
    "torch.nn.Linear(input_data, hidden_layer),\n",
    "torch.nn.ReLU(),\n",
    "torch.nn.Linear(hidden_layer, output_data)\n",
    ")\n",
    "\n",
    "epoch_n = 10000\n",
    "learning_rate = 1e-4\n",
    "loss_fn = torch.nn.MSELoss()\n",
    "\n",
    "for epoch in range(epoch_n):\n",
    "    y_pred = models(x)\n",
    "    loss = loss_fn(y_pred, y)\n",
    "    if epoch%1000 == 0:\n",
    "        print(\"Epoch:{}, Loss:{:.4f}\".format(epoch,loss.data))\n",
    "    models.zero_grad()\n",
    "    loss.backward()\n",
    "    for param in models.parameters():  # 遍历模型中的全部参数w1、w2\n",
    "        param.data -= param.grad.data*learning_rate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.3.2 PyTorch之torch.optim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch:0, Loss:1.0804\n",
      "Epoch:1000, Loss:0.0000\n",
      "Epoch:2000, Loss:0.0000\n",
      "Epoch:3000, Loss:0.0000\n",
      "Epoch:4000, Loss:0.0000\n",
      "Epoch:5000, Loss:0.0000\n",
      "Epoch:6000, Loss:0.0000\n",
      "Epoch:7000, Loss:0.0000\n",
      "Epoch:8000, Loss:0.0000\n",
      "Epoch:9000, Loss:0.0000\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch.autograd import Variable\n",
    "batch_n = 100\n",
    "hidden_layer = 100\n",
    "input_data = 1000\n",
    "output_data = 10\n",
    "\n",
    "x = Variable(torch.randn(batch_n, input_data), requires_grad = False)\n",
    "y = Variable(torch.randn(batch_n, output_data), requires_grad=False)\n",
    "\n",
    "models = torch.nn.Sequential(\n",
    "torch.nn.Linear(input_data, hidden_layer),\n",
    "torch.nn.ReLU(),\n",
    "torch.nn.Linear(hidden_layer, output_data)\n",
    ")\n",
    "\n",
    "epoch_n = 10000\n",
    "learning_rate = 1e-4\n",
    "loss_fn = torch.nn.MSELoss()\n",
    "\n",
    "optimzer = torch.optim.Adam(models.parameters(), lr = learning_rate)\n",
    "\n",
    "for epoch in range(epoch_n):\n",
    "    y_pred = models(x)\n",
    "    loss = loss_fn(y_pred, y)\n",
    "    if epoch%1000 == 0:\n",
    "        print(\"Epoch:{}, Loss:{:.4f}\".format(epoch,loss.data))\n",
    "    optimzer.zero_grad()\n",
    "    \n",
    "    loss.backward()\n",
    "    optimzer.step()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.4.1 torch和torchvision"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torchvision\n",
    "from torchvision import datasets, transforms  # torchvision包的主要功能是实现数据的处理、导入和预览\n",
    "from torch.autograd import Variable\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "torchvision.transforms 指定导入数据集时需要对数据进行变换操作类型  \n",
    "例如，数据增强（对图片大小进行缩放、对图片进行水平或者垂直翻转...）可生成新的数据集\n",
    "详见 [Detail](https://pytorch.org/docs/stable/torchvision/transforms.html#torchvision-transforms)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "transform=transforms.Compose([transforms.ToTensor(),\n",
    "                              transforms.Normalize(mean=[0.5,0.5,0.5],std=[0.5,0.5,0.5])]) \n",
    "\n",
    "data_train = datasets.MNIST(root = \"./data/\",  # .表示保存在当前文件夹的路径下\n",
    "                            transform=transform,\n",
    "                            train = True,   # 载入训练集\n",
    "                            download = True)\n",
    "\n",
    "data_test = datasets.MNIST(root=\"./data/\",\n",
    "                           transform=transform,\n",
    "                           train = False   # 载入测试集\n",
    "                          )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_loader_train = torch.utils.data.DataLoader(dataset=data_train,\n",
    "                                                batch_size = 64,\n",
    "                                                shuffle = True,\n",
    "                                               )\n",
    "\n",
    "data_loader_test = torch.utils.data.DataLoader(dataset=data_test,\n",
    "                                               batch_size = 64,\n",
    "                                               shuffle = True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[tensor(4), tensor(2), tensor(2), tensor(9), tensor(2), tensor(3), tensor(5), tensor(7), tensor(1), tensor(1), tensor(8), tensor(5), tensor(6), tensor(7), tensor(0), tensor(1), tensor(8), tensor(5), tensor(4), tensor(0), tensor(3), tensor(8), tensor(7), tensor(0), tensor(7), tensor(9), tensor(2), tensor(5), tensor(8), tensor(2), tensor(4), tensor(3), tensor(2), tensor(0), tensor(3), tensor(5), tensor(8), tensor(4), tensor(5), tensor(5), tensor(8), tensor(9), tensor(7), tensor(6), tensor(8), tensor(7), tensor(7), tensor(4), tensor(1), tensor(0), tensor(4), tensor(5), tensor(9), tensor(9), tensor(2), tensor(7), tensor(3), tensor(9), tensor(1), tensor(6), tensor(1), tensor(2), tensor(4), tensor(1)]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x11a4227b8>"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQUAAAD8CAYAAAB+fLH0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4wLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvqOYd8AAAIABJREFUeJzsnXlcTfn/x5+nVBQilSQ1drJka2TJnl3KEmMdDFkGWTIog7FkkHWIMJaxZFBkJ2ObSU12o+xG1pGyFqU6vz+ae373tFD3nmY03/t8PO6j7jmnz+ec27nv8/m8P+/3+yWIoogOHTp0qND7t09Ahw4dnxY6o6BDhw4ZOqOgQ4cOGTqjoEOHDhk6o6BDhw4ZOqOgQ4cOGflmFARBaC8IwnVBEG4JgjA5v/rRoUOHsgj5EacgCII+cANwAR4AUcAXoihGK96ZDh06FCW/RgqfA7dEUbwjimIKEAR0zae+dOjQoSCF8qndssB9tfcPgIY5HWxsbCyWKFEin05Fhw4dAI8fP34miqLFx47LL6PwUQRBGAYMAzA1NWXYsGH/1qno0PE/wcyZM+/l5rj8MgoPgXJq723+3iYhimIgEAhgbW0tAsycOTOfTuf/mT59Orq+dH39L/aVW/LLpxAFVBYEobwgCIZAbyA0n/rSoUOHguTLSEEUxVRBEL4GDgP6wI+iKF7Nj7506NChLPnmUxBF8QBwIL/a16FDR/5QoCMaPT09adasWY77vby8MDMzU6SvJk2a0LRpU5o0aYK5ubkibeaEIAhMmjSJiIgIRFFk7dq16FZnNMPJyQkPDw969uyJh4cHv/32G+np6aSlpREbG/tvn94nyb+2+qAEAQEBJCQkULt2bR49eiTbN2LECPz9/SlUqBALFy7UqH1BEBgwYAAuLi707t0bQRAQRZHw8HBiY2Px8fHh4cOHpKamKnE5Eg4ODsybNw9RFElPT2fw4MFUrFiRDh068O7dO0X7+i+zfft2GjZsSLly5UhPT0dPT4/09HTpc9UkcE8QBKZPn8706dMRRZEDBw7w7t07PDw8SE9Pz4er+OcpkCOFMmXKEB4eDsD333/P06dPZfuLFSuGj48PgiBw5swZjfoYOXIkc+fOZd26dfTu3Vu2r3HjxvTu3Zvbt2+zYMECzS7iAzRq1CjLtmbNmmFiYqJ4Xyq6dOnC6dOnSU9Px9fXl65dlYk1q1q1Kl26dKFLly4cO3aMLl260KlTJ0Xazg4nJyfu3buHKIp4eHhQrlw59PT0ZC9BENDT06NXr155bv/LL7/E1dWV3r1707lzZ1auXImenh5RUVH4+PhQuXJlSpQogb6+vlbXYWlpSVpa2gdf8+fPz5d7osCNFExMTDhw4AAODg5MnjyZxYsXy57UDRo0YMeOHVhbW5OQkMBvv/2Wp/YLFSqEv78/np6eFCr08Y9nxIgR3Lhxg4CAgDxfS04kJycDGU8lgHfv3lG4cGF69erFypUrFesH4OLFi1hbW1O0aFGMjIwQRZGZM2eSkpLCpUuX6NGjBw8ePMhTm/b29owaNYpatWphZ2dHuXL/vzrdsmVLRFHkt99+Y+jQoVy/fl3R6wGkkQCQ7U/1EUNesLa2ZtGiRYwfP57t27dL2w8cOMDUqVPp3r07X3/9NaVLlyYqKoo5c+awf/9+0tLSNL6ODzF+/Hisra3p16+fRu3nRIEaKZiamrJnzx4cHBxITk7G399fZhCKFCnCmjVrsLOz482bN3h6eua67aJFi1KuXDmmT5/OqFGjZAbh/v37xMbGSq/ExETZ3yqdP7J+/XpGjBhBSEgI7u7uTJgwAVEUKV68uCLtFypUiM6dO+Pn50etWrUwMDDg6NGjeHp6Ym9vj729PSdPnsTR0THLKOljtGvXjpMnTzJixAiaNm0qMwgqBEGgadOmHDhwgOrVqytyTSoiIiL4/fffZSODnTt3MmnSpCwjhSZNmuSpbXd3dxITE2UGQcXcuXOpX78+tra2tG3blgsXLrBixQoOHDiAsbFxnq8jPj6eyZMnExcXx9WrOS/clS9fPs9tf4wCM1IQBAEfHx9atWoFgL+/fxYLHBgYiIODA+/fv6dPnz7s27cv1+1XqlSJoUOHygzJixcvmD9/PkuWLCElJUXavnnzZunLkpSURHS0snleoiiyevVqVq9eDWQ8XZWicuXKeHp6Mm7cOCDjWpYsWcKFCxekYywtLaUbuVWrVrn2ybRq1Ypt27bJnKKvX79mypQpsv9V165dad++PeXLl8fHx0fxJ52/vz8///wzkPFZ7tq1i6CgINLT02UjhUWLFuW57eTkZJKSknLc//79e8LCwggLC6NIkSLs3bsXd3d3tmzZkqd+0tLSWLhwIcuXLyc9PV16SC1dupTBgwfn+bzzQoExCmPHjmXixIkAXL9+HV9fX9l+BwcH+vbtC2QM6fNiECBjGL106VKKFSsGwNWrV/nhhx+yjAo6deqEnZ2d9P7ly5dERETk+XpyolWrVvzyyy+ybarphLY4OTlJUyuA/fv3M3DgQGl/6dKlWbFiBeXLl6dOnToArFixItft7927lyJFigCwbds2Ll68mK3PZf369bx7946oqCi+/vprbS4pWyIjI4mMjAQyrlk1ndDTyxgY79q1Cw8PD8X7zczbt2/Zv38/d+/e1bgN1f/+/fv3AAwbNkxmFA4fPqzdSWZDgTAKJUuWxMfHB4BXr15lsZRdunRhzZo1AJw7d46TJ09q1M+NGzcYMGBAjvtHjRqFn5+fbDgoiqJsFJFXypYtS8uWLXF3d6dVq1aYmJhw9uxZtmzZIn0hp06dqnH7Kho0aMCuXbuwsrLixYsXTJ06lcuXL2NiYkK5cuUYO3YsjRs3pmbNmtLf/PHHH1y+fDnXfagMwqJFi/D29v7otOr8+fO8ePFCswv6CE5OTowbN46GDRtKIwTIGEXs2rVL43bj4+PzdPzixYs17utjRERE8MMPPyjeboEwCtHR0Zibm3Pr1i0qV64sba9bty7z58+ndevWABw7doyuXbt+cHinCUOGDCEwMFAadgJcuHCBNm3aaHxTb9q0SRrZXLlyhffv37NlyxauXr3KN998w/Lly1m2bBmiKKKnpyfFK2jKxo0bsbKyIj4+njlz5lC4cGHCwsJ4+vQpAQEBjBkzBoCQkBA6dOhASEgIPXv2zJO/pG7durx69eqDT8amTZsSHBys8XXkxG+//cbnn38u/Y9Uo4L09HTOnDnD4sWLtTIGKk6ePEnFihVZuXIlzZo149atW9y7l5FnVL16dfbs2cN3332Xb8ZOHS8vL6ytrXn79i1v375VrN0CYRROnjyJh4cHJUuWlD01vb29MTU1BTIs+MiRIxU1CPb29gwaNIgxY8ZIQ9AXL14wYcIEDhw4oPE/vlChQlStWpWkpCQGDBjA3r17JYepvr4+zs7O2NraAv+/AhEQEMDz58+1vqZSpUpJc+nr16/j7OwsPf2+++47OnToAGQYvbw6UC9duvTRY3x8fBQN/nJycmL79u3Y2trKjIEgCERERNCzZ08ePnz4kVZyR9euXSlbtiwDBw7kl19+wdHRkZiYGMlfYmxszA8//MC6desYPHgwL1++VKRffX19aRlXfbqnmrZu27aNIUOGaDViVadAGIVt27bRs2dPzMzMmD17dpb98fHxdOzYkZs3byrSn5GREQ0aNGDbtm3S/BsgJSUFLy8vfvrpJ63aNzQ0xNHRkaioKK5fv05qaio1atRg7NixuLq6YmlpCSAFS0HGspemS1sAvr6+MifqkSNHCAoKkgyCpaWltH/u3Ln5En9Rq1Yt2rdvL11TXp1vOaEy2NktOypJVFQUbdq04bvvvmPGjBlZ9iclJTFixAj27NnDnDlzmDp1Kq9evdK631WrVjFo0KAc93/xxReMGzeOZ8+ead0XFBCjsGfPHnr27Enfvn3p0qULAKdOnZJWIo4cOUJUVJQifbm6ujJ8+HDatm0r23748GECAwPZs2eP1n28f/+emJgYHB0diYiI4OHDh9jY2GBsbCwZAnWDABmGRBtCQkIICQnJdl+RIkWkJ/hff/2Fn5+fYk8ddTw9PWVGTuUM1IaIiAg+++wznJyc8PLykoyBh4cHtra22NjYKDZS2LhxI506dWLdunU5HpOcnEy/fv2IiYnh6tWrisSvdOnSRRoxqnj48KH0P7px40YWh7g2FAijABAcHJxlLqp6EuQ1QCk7bG1tcXNzY/bs2dmuK2/atInU1FRZNF6vXr0wMzOjRo0a0rbcrBu/f/+eCxcuUL16dUxMTKhSpYq0TxRFRFHk6NGjXL58mXfv3uHr68vEiRPZv3+/5IVWCiMjI+bNm8fXX39NfHw87dq1U9wnAxnz327dugEZy22RkZG4uLhw4IAyOXMRERGymIr79+/j4eHBuHHj8hxrkRM3btyQVmU+xLNnz5g1axbz5s0jODiYv/76S6t+ly9fTvPmzQkJCWHatGkYGBjg7Ows+TKUpsAYhcx8//33APz444+KWON9+/Zhb2+f4/4tW7ZkeXqrk5iYmCdP8JgxY0hPT6djx46Ympry6tUrkpOTuX79Ol5eXtL83MjIiMGDB9O4cWNatGjB0aNH83ZhH8Hf358RI0YQHx9PmzZtuHLliqLtQ8a0Yfr06ZL/R19fn5kzZ/L+/XuSk5O5c+cOfn5+2QYF5QYnJyfKlStHeHi4NCrYtWsXEyZMkOIV/mnWrFmDl5cXDg4OHDlyRKu25syZw5w5c7C1tWXGjBlMnDgx3wwCFLCIRhWurq6MHTuWN2/eMGvWLEUiCtWf9nlhw4YNTJ06lb59+0rLprkhISGBAQMGUL16ddq1a0eNGjWwtbWlZcuWModdcnIyAQEBiKKIu7u7Ruf4IUaMGMFff/1F69at87T8mBeOHDkiGQT1YbCBgQFFixaldu3abNq0icuXL8tWlz6Ek5MTQUFB/PzzzwQFBTF27Ngsx4iiiJeXlzIXkUdUKwKqVR0lqFOnDqVKleLx48eKtZkdBW6kYGJiwpIlSzA0NGTcuHGKWcyuXbuyYcOGj6YoP336lLCwMCDDeffw4UOtHIBxcXFZgpUys2TJEvr27UuvXr1YuHAhd+7c0bg/dUaMGAFk+BvyY4Sg4sWLF5QuXRqA4cOHy6Ina9euTb9+/TA2NqZBgwaEhYXJgsNyoly5cpQrV45GjRohiiKRkZGUK1eOxo0bAxlGQxAERfwW2pA5e1cb2rVrB6D1yONjFDijMG3aND777DMgI6xZKfbt20ebNm0+Gqf+6tUr/vjjD8X6zQ2JiYncuXOHatWq0apVK0WMgr29Pd9++y0BAQFSpGh+0aFDB7Zt28aPP/4oBZmp+P3331m7di1FixalU6dO1K9fP1dtqnwvqpUH1chBPU1aFMV8DR76EEWKFMHY2FjK5lUCdd9TflKgjEJYWJi04gCgp6en1VM6MxcvXlSsLaXp378/d+7coXnz5loFMRkYGODj48O0adNISEjIlzDjzPz555/ZpoOr8+bNG7Zv355rv8LOnTu5f/++9KWzsbGRLUGeOXOGCRMm5NtIoXXr1gwYMICHDx8SHBzM2bNnsbGxwdLSksGDB0srZb/++qvifXfp0oW9e/cq3q6KAmUU2rRp82+fwr/G8+fPKVmypNbtTJw4kWnTpgEZyVAFmcjISK3rFmjKsWPHOHnyJFZWVjRt2pRx48ZRoUIFGjRowI8//kiVKlWIi4v7V85NWwqUUdChParh+eHDh/P1afO/QGpqKg8ePCAoKIigoKB87y8kJAR9fX3Jp5Vf6IzC/xg9evT4t09Bh4asXLlS8SI72ZEvArN5xdraWtQpROnQkb/MnDnznCiKDT52XIGMU9ChQ0f+8UlNH/6rcl26vnR9fQp95RbdSEGHDh0ydEZBxz+GoaEhhw8fJioqSip7p+PTo0AahVWrVv1rMe2QUUJNVXjl0aNHNGzY8F87l4JCkSJF2LdvHy4uLri6uvL69et/+5T+E0RHR5OWlsa9e/dynTfyMQqkUYAMkZF/i3bt2rF48WLS09O5fv26IlFzAwcOJDQ0lF9++YXhw4cTGhpKWloaoigSGhrK8OHDFQvUMTQ0ZNKkSVIZO3UaNWrEpEmTSEhI4JtvvlGkP4BZs2bRunVrAgMDefLkiWLt/i+zdOlSKlWqhCiKlC1bVrEqz5+UozG3PHv2jClTpkgJPf803377raLtGRoa0q9fP1q2bMnNmzelFGxVXH/Hjh2ll6enp9ZZcn5+fowbN46wsDBiYmJo3749HTt25PDhwyxZskQqwDpr1iwuXLigdQKOnZ2dVBA3KipKcZ2MfwsjIyNcXV2BjMQ2CwuLLMfs2bMnXwrWDBo0iOHDh0vl5yAj/HnKlClat10gjUJwcDCTJ0/+x/stXLgwGzZswMbGBsgYuimhWWBpaSlpO6iGgA8ePJBq8Dk5OWFjY0PHjh3p3bu3xkk+JUqUwM3NTSrt1aZNG86cOSMJtqiKoKi4ePGi1gVsTExMmDZtGqVKlQL4YNWifxJDQ0Pq1aunUXn+KlWq4OvrS/v27SUB4/fv32NgYCAdo6q9cfjwYSZOnEhMTIxi5w4ZFb4zjxw/JBqTFwqkUYiNjeX+/fsUKVJE0Sq2H6NKlSr07NlTen/gwAFFSn29fv2amzdvSgbBxcWFS5cukZCQQNGiRTl16pRkiDTliy++YNKkSTg4OMi2Z6fgpOLatWtal/lSFb8F/tH/1cdo06YNu3fvznOZu8GDB7NmzZosox11g6BOu3btMDU1Zfz48fz+++8an6863333nVTYV52tW7cq0n6B9Ck8e/aMNWvWZHmy5Sf29vayEuEqoQ8lePnyJVeuXEEQBC5cuMDx48epXr06x48f59WrVzg4OCAIAomJiRprWkybNi2LQVDx+PFj7ty5w507d3j+/DmvXr1i2LBh2RYuyQtGRkaykdS2bdu0ai8nhg8fjpeXF/fu3ePly5e8fPmSs2fPZiv44uLiQlhYGMHBwXn20QwePPiD1bXevXtHQEAAAQEBsvR6Jycn+vfvn6e+PoSPj0+Wc7927ZoiZQmhgI4UIOND6NOnj2IVgT/G/v37ZdY5PDycU6dOKda+qjZA3bp1mTVrFp6enpiZmUnbg4ODmT17tsbVkXJSmVI5EwMCAnjz5g316tXD2NhYkZRfZ2dnKTX75cuXLF26VOs21alTpw7jxo2jb9++WQqb1q1bl59++okVK1awZcsW4uPjGTNmDCYmJhgZGQHkKe1+6NChrFq1Cvj/6lFpaWkkJiZSvHhxvvrqK9avXy8d7+bmxk8//STV5xg5ciSjR4/W6noBmQ9BRWJiIv7+/v9b1ZyzIzg4+B+rv6evr4+VlZX0/ty5c4rrH6qj7izasWMH+/fvJyQkhDdv3mjcZs+ePTl69GiWqkaqWpdjxoyhXLlynD9/XuM+MqNeEXvXrl2KFqfZtGkTrq6u2cY7+Pn5kZaWhq+vL2ZmZjl+Gf38/HLVl42NDfPnz5emDK9evWLFihXs37+fFy9eYGFhkeUBsXv3boKCgj5Yml0TsmvvyJEjMoOkLQVy+qBCEAT+iUSq0aNHy+ae8fHxPH36VNE+MhdOUUnY9e7dm59++kkrgwAZS7jZzUNVlC1blsTERJycnLTqR50WLVogCAKCIHD69GnF2gXo169fjgZh5syZTJ8+nQoVKuDs7MyWLVuIjY2VjlGVYff3989VX4cPH5b15ePjg6+vL2fOnCEmJibHEaPSZdMEQcDR0VG27ffff2fkyJGK9lOgjUJsbCxeXl7SElp+0LFjR+nJHRsbS/Xq1SUVJaVo27atZBQeP35MQkICw4cPV8xxBHD69OlsK0sdPXqUhIQEICPAKDw8nLi4OK2Vrt3d3alfvz6iKBITE8OmTZuAjHiMJ0+ekJaWxt27dzl06BBpaWl59pxfu3YNgDt37uDk5IStrS36+vr4+vpKZfDv3buHtbU1rq6u2NrakpSURP/+/TE2Nmbbtm25FmpRj4kJCAjIdfqy+pK5tqOkatWqkZqaytChQ9HT00MQBK5evYqrq6vixVy0MgqCIPwpCMIVQRAuCoJw9u9tZoIgHBUE4ebfP7UvF5QDp0+fpmrVqlSrVi1f2m/Tpg07d+7E3NycGzdu0K5dO27cuKF4PwcPHqRs2bL88ssvNGnSBDMzM+bOnauosXv16hVz5szh4sWL0qtDhw60a9cOFxcXSdcSMqTlpk6d+tF6lR9CvTq2agnV0dGR9evXY2FhwaFDh3Bzc8u1zH1mWrVqhaOjIy1btiQqKirbVSAHBwfWrFlDsWLFSExMZMeOHRoZWtVo5+3bt7leDjYyMsLExESRkZKlpSVffPGFrCbl6tWr+fzzzxXzI6ijxEihpSiKddTytCcDx0RRrAwc+/t9vrB7924EQZBk1pTE2NiYYcOGSU4pNze3fDEI9erVk36fP3++NMxVaSQqSXBwMPXq1ZNeKhnzCxcusHv3bpmPpnXr1jRt2lTjvtRDv589e8aMGTMIDQ3l3r17zJ49Gzc3N0qWLMny5csBuHXrVp7af/LkCefPn+fBgwc5HjNz5kyKFy8OZCiKaRrxp/oy/v7777kumluxYkVppKRtsFZAQEAW+YCoqKgcncfakh/Th67Axr9/3wi45UMfQMZNLoqiIlFcmalSpQrdu3eX3udVgjy3qObdkZGRUpmtjRs3IggCnTp14tWrV9SuXVuRvipWrMj06dOz9WC/fftWijpUoS7mm1d+/PFH6fcZM2YwdOhQLC0tWbBgAaGhoQwZMoQlS5ZQpUoVwsLCGDJkiMZ95YRKYvDEiROKOPw+++yzXIvjfv7557L3msZoWFhYUKFCBdm2cePGsWHDBo3ayw3aGgUROCIIwjlBEFQev9KiKKricJ8ApbXs44OsWbMGZ2dnRR2O9vb27Ny5U7YtP4ZpAHv37s3yNPH29mbx4sWIoihFBBYtWlTrvtavX8/06dMZOnSo1m19jNevX/Pu3TsgQyFKtXrj4uJCeHg4K1asoFatWiQlJfHtt98q/vl27doVyFiumzFjhiLzbjs7u1zFG7Rr105mUI8ePcqsWbPy3J+FhQVbtmyhVq1asu3Lli3L11BxbY1CU1EU6wEdgFGCIDRT3ylmnHm2Zy8IwjBBEM4KgnBWG+3CkJAQxT+g+vXryzQhT5w4oWj76qiUsmvWrCkpQCUkJDB37lzpaeDu7q7RTZUT2bVlYmKiiHiuirCwsGw1D9zc3ChUKGMl/Pjx43Tr1k3xMuxdu3Zl/fr1xMTE0KlTJ0VXPqZOncoff/zBlStXpJevry8jR45kwoQJXLlyhXXr1lGxYkUgI6BpypQpGmWF9u7dWyZpAOSLzmdmtDIKoig+/PvnUyAE+Bz4SxCEMgB//8x27U4UxUBRFBuIothAG4fW4cOHuXjxIrNnz5bNz7VBPWQ1LS2NOXPmKNJuTpw/f56iRYvi6+srjQgSEhL46quvpLiBr776SiZuqwnz5s0DMhyJt2/fplevXlhaWjJmzBiio6MlBSIVqhgGTRk0aFAWP0xycjKvX7/Gx8eHHj16KK6N6erqyoYNGzA1NWXRokWKGIQ9e/ZIDkMzMzPs7e1lr5kzZ7J8+XLmz5+Pvb09ZcqUISUlhX379mFubq6xnsjw4cOzbNu4cWM2RyqLxkZBEAQTQRCKqX4H2gJ/AKHAwL8PGwgo9/jJAVEUKVWqlGLD4hUrVki/BwcHf1TWTVtatmzJlStXqFOnDsePH8fGxgYbGxv09fVxd3dHEASMjY21Xia8e/cu8fHxCIJA+fLl2bZtGydPnmTJkiVZciDWrVundcTmgwcPZL6F5ORk2rdvT4kSJZg3bx4vXrzQqv3MmJiYsGjRIsm5eObMGUXa9fT05Ny5czJVqg+9bt26hbOzM25ubtIUSgmeP38uOWbzE20iGksDIX+HfBYCtoqieEgQhCjgZ0EQhgD3gKwB6Arj5+cny0soaKiEcufMmUPdunX5888/gYyEK3Wla22nSTExMbRr147Ro0czcGCG3c5cl+LZs2eEhoYyZswYRRKYFixYwIIFC7Ru52M0bdqUWbNmyaZ9H9MFzS3Pnj2jRYsWVKpUCTc3N2rWrMnbt29xdnZmw4YN1KxZE2tra44cOcKjR4/Ytm2b1olkhQoVkjmE//rrL6ZOnZovK2BZ+tb0D0VRvANkybARRTEeyFq9Ix8JDg7OEvuuDfkZDJUTwcHBBAcHAxlPpkqVKlG/fn2aN2/Ovn372LVrlxQApA3nz59n0KBBkjc+MDBQ0lxUBQQVRIKCgihTpgyQEYbeuXNnRaNOk5KSuHz5cr4pc2cmNTWV6tWr/yN9ZabA5j78l1m9evU/1td/RW9Dldz0/v17Bg0apHgY+v8SBTrMWYcOFa6urnz77bdYW1srVmzkfxXdSEHHf4JLly5x6dKlf/s0/hPoZON06PgfQScbp0OHDo34pKYP/1W5Ll1fur4+hb5yi26koEOHDhk6o6BDhw4ZOqOg4z9LYGAgz58/l2o0bt26lVu3bmXJOiyo2NnZMXr0aEUKwqpTYIyCnZ0dU6ZMYdiwYfTt21eSVEtLSyMtLY2FCxfKiqsWRBo0aPDBOoqQUbvx+vXripaEGzJkiPR5XrlyRbrRmjVr9vE//kTp0aMHzZo1IyQkRCqHfu/ePUxNTalfv/6/fHbK0KNHDxYvXszixYtZu3atYtf1STkac6J8+fIcOHCAypUrk56eLt3A6enpUj6Al5cXw4YNY82aNfzwww/cvXtXkb7t7e0BZMUx9+zZk235r8TERO7du6dRP2vXrqVhw4b06dMn2/0GBgYMGDCAihUr8ubNG9zc3Dh48KBGfalz/fp1ypQpI32e1atXl0qOqTQn7t+/n6c2DQ0NMTAwoHDhwlLB2fyqEpQd69evZ8CAAVhYWPD8+XNp+5QpU2jYsCFNmjTJ1yIl/xSqVHvIyEitUKECnTt31jq9ukAYhTp16kjqSa9fvyY+Pj5LNRrIKKE2duxY+vfvT2BgIL6+vhr3aWFhwahRo5g2bZosEUkQhCwpraqkpZcvX7JlyxbGjBmTp76aNWuGu7t7jpqD1atX55uf67JxAAAgAElEQVRvvuHs2bOMHTuWe/fuaZ1haGBgwIQJE6S8/+wwNTVl9OjRTJo0KVdtmpmZMXLkSDp16oSVlRW2trZcunQJURSJjY3l5cuXhISEEBYWpnXCUE589913DBgwgHPnzkkFaVXUrFkTZ2dnbt++rVUfX331lZRe36xZM27evCnda4IgMGXKFClNPT+ZM2cOe/fuld43a9YMZ2dnqcyepnzyRmHq1Kl4enpK74sUKYK5uTmpqalMmjSJV69e0blzZwCaN29OyZIlMTMzo3///hobhQ4dOrB582ZMTU2lbd999x2QMWLIHPClMgqZlYFyy4sXL7h16xahoaFcuXJFtq927dp4enrSr18/ypUrl61StCZMmDCB2bNnf/S47t2759ooHDx4kAYN5LExderUQRRF6tSpA8CAAQM4deoUI0aMUFxfsXHjxowfP5779+9nO73y8PBAX1+f3bt356ndzJGS9vb2Ugaj6n+vnsk6ZswYTE1NFSsTaGpqyoABA1iyZAmiKFKvXj0uX77Mn3/+yb1792RaHs2aNfvvG4Xhw4djbW0tvX///j3Hjh1j7ty5XLhwAYANGzZQsWJFtmzZkuWmzCsWFhbMnDkTU1NTHj16xK1bt7h586ZkFFQ/lcTe3h4LC4tsVYuHDx8uJS0pkQlau3Ztjhw5km2twUuXLnHy5ElKlCghlR3LLB7zIRwdHXOV3u3s7Iy/vz/u7u6KTiu2b9+OsbExI0eOzFJT09DQkK5du5KUlJSncGh9fX1q1qyZp/MoXbo0vXr1Yv369VqlOtvZ2dGjRw9GjRol8zUdOXKEli1bEhMTQ9++fWVqXuPHj+fatWv89NNPGvf7yRsFdRITE/Hy8pKp4VhaWuLu7s6kSZMoWVL7avKjRo2SKjj17t0725JiSmJubs7mzZuBjDJwzs7OUrUgc3Nz6cubkpLCy5cvteqrevXqWQxC48aNpfqIL1++JD4+nkqVKmmkfdisWTOqVKkCZPgqbt68KctWLFKkiDTfbd++PWPGjFG01oKRkRFHjx7l0KFDWfYtW7aMWrVqMW7cuA9WgM5Mo0aNNDoXOzs7SRogrxgYGGBnZ0dISEi26dPm5uZ4enri5eXF5cuX+fHHH6XCtwYGBkyaNOl/xyhcvXpVZhCqVavGhg0bsh0daFqo8+rVq9ITWd1JlV+o/9Mz3wDVq1ene/fuiKJIUlISixYt0qovT09PmUGIi4tTTAkZ4Ndff82iQVmoUCGcnZ3p1q0bHTp0kIbaKSkpiiYwNWrUCDMzMw4ePEh6erpsX4UKFejSpQv379+XalYowR9//IEgCMTHxyu6UmNnZ5fr2hZJSUkMGzZMVg1b2zoMBcooVKhQgZYtW3L8+HEg4wucebiakpJCdHS0TDI+L9y/f5/k5GQMDQ1Zv349ffv21dox9SF69OghGaH4+HhplFCpUiVOnDiBnp6eJP6hTb1Bc3Nz2Y0bFxcn03pUEisrK5YsWYKRkRGlSpWiSZMm0r74+HjCwsJYtGgRUVFRivVZpEgR9PT0sq22NGPGDMqUKUPHjh3zvJKinoadmprKo0ePiIqKYsOGDZL/KCEhIcso7sSJE0RHR+f5Ouzs7CQ9k8yo7gWAsWPHsmzZMkmH4vTp0zg7O+e5v+z45I2Cra0t7u7u7NixA3Nz8xwLfV69ehU/Pz+CgoK06i8iIgJjY2MaNmyIt7c3J06ckCr6DBkyRLHCmQ0aNGDBggU4OztL2hXq9Qx79OghLRNevXqVyMhIBg8eLDsmtzRr1ox9+/ZJik+rVq3C29s723JrHTp0kHm0T548mae+TExMOHPmjDQHFgSBBw8ecO/ePbZu3UpKSgo7duzItWRbbvnll19wcHDgt99+w9PTE29vb+7evUtYWBgnTpzA1tY2T9MGFc+fP8+zZD3Ao0ePNFohunLlCsbGxrKH3aVLl5g6dSqCIHDy5Mks/zdzc3PpPlKCAhG8FBISwpgxY4iNjZWq6gqCIGnq/fTTTzg4OGhtENSJjIykR48eODo6SuvBqmKmmase55VmzZrh4+Mjs+zPnj2TnGPTpk2TSeHZ29sTHBys8ZPdxMREJgG3aNGibA2CqampJB2vIrcyaSoSExNp3rw5q1evJjw8nKdPn2JjY0OTJk1YsWIFa9eu5cWLF9y9e5e1a9fi7u5O48aNKVy4sEbXps6VK1ckAZ1Nmzaxa9cu9PX18fb21sggaEN2cSy5IXNl8+joaNzd3Tl8+DCHDh3K1iAoLWT7yY8UVKxcuRIrKyuZyIYqeKl3797cu3ePCxcuKKpdABnyZHv37mXevHlMmDABZ2dnli9fTuvWrfM8FFUxevRoXF1dZdvWrl2bRXka/n/IGBoaypMnTzTqr1u3brk6bvfu3TJDderUKY0qOsfGxkrBXqVLl8bS0lIKyho5ciRFihTB1tZWqhUpCAIxMTFs3LiRwMBArWIwTp8+Td26dQkNDaVVq1b06NFDa3FXTdBUFkB92nDp0iVcXFyyVSerXbs2zZs3x9PTk+rVq8umFtpSYIwCZESq9e/fP0s5cgMDA3x9fUlOTmbBggWsW7dO4y9sTvj6+rJ79262bt1KhQoVmD17tlQROa/kRV8wPT2dp0+fEh0dnUVPMLc0b95cutmWL1+eRQ/R1NSU3bt306JFC+nGOnXqlNYl5SGjCvFff/0lrdmvXLmSypUr07VrV8qVK4ednR1169alWrVq+Pn54erqqpWGJWQ4N1UrUZo+sfOK6vNVBS9pIv4C8orddnZ2dOzYMYt61pIlSyhRogSlSpWS/kb1gFSt0GlDgTIKz58/5/Hjx1mMggojIyN8fX3p3Lkz7u7ueTYMhQsX/mCd/rNnz3LkyBGGDx+ulZMuLi6OxMREHj16lO1+VfQmZAivnjhxQmODAGQJrlHH3NycjRs34uzsLLuxtF3pyIn79+9z//59SUvDyMiIChUq4OHhwbfffqvxEqAKOzs7jhw5In2G7du3V1yBKjPFixeXfb5Kze1NTU1lq23q5f7Vf1dn+vTpsr/RhALhU1CxePFiSbgzNTWVhg0bMm7cuCwBMHXq1NFIsfnXX3/FxcUlV8dqEzW2evVq+vXrR/Xq1bN9qePj4yOL6FSajRs3ynwkZ8+epVOnTjJnY36SnJxMfHy8Isu/tra2HD16lAoVKvDNN98AGf6o/EbJrEttNDWjo6MVWXItUCOFsmXLyt6fPXuWs2fPcu7cOb7//nvZUybzsbnBwMCAIUOG5ErKLLeS5NnxIf2ApUuXyuaVmYVulaRHjx5ZnKarV6/OEmugFHZ2dtSvX5/KlStLT/IqVaoo4jk3MjJi//79VKpUia+//pq1a9cyderULPkP+YGSmiP79u3LUSFb3W+Q2YfQq1cvxe6VAmUU1q1bh6OjI8WKFZNESgHCw8OzOOE0CV4KDQ1l6tSphIWFZev0CwoKolevXrx48YJly5bl/QI+QualpdzkJuQGLy8v9u3bB8CYMWNyTNhS8sZSp3v37jRu3JgBAwZQqlSpLAZAfcidnVPtY+jp6bFnzx5q1KjB/v37iY6OJioqivnz5/8jqw7q1/Pw4UNWrlypcVujR49mzZo1bN26NUuIuXpW8P79+xFFkbi4OGbPnq3VQyozBcoobN++nfT0dNatW4exsTEbN25kxowZjBs3TpYodO3aNY2Cl/z8/KhatSrLly/H3NycFStWSA4jJycnGjVqRHp6OitWrFBcBxGgZ8+exMXFcfPmTVavXq1o9N2HnsQHDx5k7Nixit5Y6uzatYvY2FhOnjzJhAkTSE5OpkaNGly4cIGUlBTCw8OlcGBN0sGnT58u+XgMDQ05cOAAhoaG+Pn5KXodOaE+UkhLS9PYyQjw9u1bIiMj6dSpE/3795etHOnp6XH16lVWr16tddLThyhQRgFgx44d7Nixg7Zt2zJ58mQWLVpEly5duHr1Klu3buXx48cay6slJSXh4ZEhfTl9+nQePnyIiYmJ9IVav369YiK22REQEEBAQIDi7R48eJBu3brh5eUlRTXGxcUxZ84cfvjhB8X7yw5V9GJoaKjibU+fPj3PxUmVRD2/w8bGhnHjxuU5viMz165dw8fHRysHs6YUOKOg4siRI4oHbagzc+ZMLl68SJs2bYCMpbT8DHfOb0JDQ/PlC6kDWdKTnp4ehoaG/+LZaE+BNQr/BHv27FE8GErHf5uEhIR8Hdr/E+iMgg4dCqBJfsSnik42ToeO/xF0snE6dOjQiE9q+vBflevS9aXr61PoK7foRgo6dOiQoTMKOnTokKEzCp8IRkZGNGzYEDc3Nx49ekR6eroUvVnQ173/KczNzenQoQMrV65k+/btiKLI77//LpWXL8j4+flJQkiqV3p6OnFxcRw6dIjSpUsr1tdHjYIgCD8KgvBUEIQ/1LaZCYJwVBCEm3//LPn3dkEQhGWCINwSBOGyIAj1FDvT/zCVKlXiypUrhIeHs2vXLl68eMGdO3cQRZEvv/yS06dPK1KZ6N+iQYMGDBs2jK1bt3Lx4kWSk5PZunUrW7dupW/fvlq3r6+vz+TJk7l9+zb79u3D09OTHj16EBcXR/369enatasCVyGnUKFCeHt78/r1azp16vTB45RImDp27BinT58mJSVFql5+7949zMzMcHFxISoqCgcHB637gdyNFDYA7TNtmwwcE0WxMnDs7/cAHYDKf7+GAcrH7P7HqFu3Lhs2bKBSpUo8fPiQWbNmYW9vT/Xq1WnSpAkPHjzA0dERb2/vf/tU80zZsmXZvHkzZ86cYdWqVfTu3ZvatWtjYGBA79696d27Nxs2bMgxKzC3zJ8/Xwp5r1mzJlWqVKFKlSr5FkRkaGjImjVrmDdvHsbGxkyYMCHHY0eMGIGlpaXWfYaFhdG6dWusra0JDQ3F39+fmjVrsmLFCt68eUPZsmUVkyP46OqDKIqnBEH4LNPmrkCLv3/fCJwAvvl7+yYxI/ghQhCEEoIglBFF8bG2J1qsWDGGDRuGhYUF+vr6sn+EIAgsX76cyZMna62jBxkpvn369KFPnz7Y2Ngwf/58zpw5A2RU6VUKKysrDh48iIWFBceOHZPESiBD9CYiIkIKitGmkvO/hYeHB3369JFqMp49e5awsDDKlCnDuXPnmD17Ns2bN2f8+PFs2bIlR9m8D2FtbU2fPn0YNWoUW7Zske1T1dlQSbwpRc+ePRkwYID0fv/+/TkeO3r0aH7++WdF+k1NTZXVnUhMTGTMmDGUK1cOV1dXxUaTmi5Jllb7oj8BVBOasoB6uaMHf2/T2CgUKlQILy8vvLy8pKrKIM/6E0WRUaNGUb9+fTp06KBVpeC+ffvi6+srq340a9Ys3r9/jyiKzJo1i4sXLyoi7vrkyRPOnDnDzZs3s5Vm69OnD9bW1lI1JKUxMzPLUm9AEASKFi2KlZUVJiYmlCpVCiMjIw4cOJDn9lXD6iVLlsiUtSpUqECxYsVYtmwZzZs3p0aNGpQrV06j3JJHjx7RsGHDbB8Gu3btYtCgQfTo0UMrXVF1fHx8GDt2rPT+4MGDLFmyJNtjS5QogZGRkSL95kSFChWoW7euojUdtI5TEEVRFAQhz2GRgiAMI2OKIdNszIyXlxfff/+99P7p06eSsvPWrVvp06cPtWrVonDhwjg5OTFx4kS+/fbbvJ4OkPEBz5s3T2Z8VBgaGkpG4d27dyQkJLBgwQL++OMPSpUqpXEdAnXlYHXMzMz44osvEEWRY8eOKaqRoKJfv34sW7YMFxcXSXuxYcOGVKxYEVNTU+mG3rlzp0ZGoXjx4gCygjJWVlZcunSJN2/eyJS8tSE2NjbHfYIgSKM8bShcuDCdO3dmzJgxUm3EH3/8EX9/f9LS0rL9m7Zt22JjY6N13zlRoUIFDh48SLly5RBFMcfCPXlFU6Pwl2paIAhCGUCVO/oQUC+gaPP3tiyIohgIBEJGmHNOHZ0+fZqIiAgqVarE5s2bWbVqFTdv3pT2L1u2DEdHRw4ePEjJkiUZMGCARkahfPnyjB49WmYQIiMjef78Oe3by10qhQsXxtraGj8/P1JTUylcuDAODg5MmzYtz/3mhL+/Px07dgQy5qVKU7ZsWZYsWcKNGzfYtm0bpqamhIWF8eLFC86dOwfA8ePHuXv3rsZKTqmpqQC0atVKEnXt2rUrJiYmkmHPTxo0aMDdu3dzLZD7IVq0aCEr8bd27VrGjh2bY01Pc3PzfPm/qWjatCmbN2+W6pU+ePCAL7/8UpG2NTUKocBAYN7fP/eobf9aEIQgoCHwUlt/QmRkJG3atMHKyoq7d+9me0xUVBTv37/Xphs6duzI6NGjpfZGjx7NrVu3SExMxMHBgRYtWuDi4kLTpk2lJ6j6HE4lW68Effv2lUqir1+/XvGUbUEQ6NWrFykpKYiiyMiRI4mLiyMsLEzRfnbs2IGTkxMNGjSQKXOnpKTQt29fjath55aJEycSERGhsYSgilKlSsm0GX/77bcPGgTIENtVUkpOhZmZGTt27KBJkybS6DU8PJwpU6YoJsP3UaMgCMI2MpyK5oIgPACmk2EMfhYEYQhwD/D4+/ADQEfgFpAEaOdW/pu3b9/maBCUQjXMCwoKYuHChVy8eFHap6oFuXDhQmbMmKHY/DQ7LC0t2bx5M0lJSUyYMCFfiqA4Ojoyf/58Ll68+I+k+To5OXHy5ElJXevmzZtcuXJFqiqUkJCgVbWinChWrBi7du3Sup3Ro0djZmYGZJSsnzBhwgcNAmSohStFgwYNmDJlCqVLl8ba2jpLmbbdu3crWlfzo0uSoih+IYpiGVEUDURRtBFFcZ0oivGiKLYWRbGyKIptRFFM+PtYURTFUaIoVhRFsZYoimcVO1M1jh8/LgviSEtLk5Z9ypUrJ9ueW1RDvbt372ZbGt7a2poTJ07I1qTv3LnDZ599RqFChaSbRlO+/PJL3rx5w+PHj6lTpw4mJib5YhDmz59PZGQkenp62NnZMXXq1DzJzeeFxYsXU6JECaKjo2natClr1qyhc+fO1KpVi/T0dKmyULVq1WTVi5SgS5cufPvttwQGBmrdlrrYa+nSpVm3bh1Tp07NVrcS4MCBA3Tu3BmAGjVq8Ndff2ncd0hICJGRkbi5uVGlShV2796Nubk5+vr61KlTh4SEBNnqmBJ8UglRuaVYsWI57nvy5Im0bFO+fPlctxkaGkqfPn2YMmUK/fv3JzQ0lC1btvDgwQO++eYbRo0alaXO4bNnzxQpDDplyhTmzJmDKIqsXr1aUTXmzNjb20vXUaJECWbPno2Xlxfbt29n5syZWpUYz45KlSrJdDrUBVszLyEqhZGRkSTNpwR79+4lMjISJycnAGrWrMns2bOlwroLFy6Upq+lS5eW+aAMDAxy1GjIDXPnzuXXX3/l5s2bWSpnXb58mbi4OMzMzCSnrhIUSKPg6OgoPbEdHR2l4fytW7do27at5MTKi9LQkiVL6Ny5M8WLF8fGxoaRI0cq5h3/EDY2NgwaNEhaZcjvIKW+ffvSu3dv4uLiOHPmDB4eHixevJhRo0Zx5coVRZ6s6nh4eEhGPCUlheDgYBo2bCg5IfODTp06MXjwYMU+y8TERDp06MDEiRMZPHgwpUqVksU+ZA5eUjcAFy9exNraWuPRQlRUVI4rT23btqVSpUoAWVSvtaFA5j6Iosi+ffvYt28fVatWlbarGwQgT/Os8+fPY2Zmxpo1a2QitpnFbFWve/fu0a9fP62vZfv27VSsWJHIyEjatm2bL/EI6rx8+VKqFP348WOZoGmrVq0U7atkyZJ89dVXQMbN3alTJ+rWrcvkyZM/8peao6+vj4eHh6IrQZDxuU2bNo2yZcvSrVs3hgwZQlRUFLdu3dKoLH1OmJqa5joC0tfXVwpuU1+215YCaRRUDBw4UJq7RUdHK6If+fXXXzNlyhR++OEHbt++jSiKnDx5kuXLl0veelEU2bx5s9Yl0atWrUqVKlWAvC87Wltb5+q4ypUr07BhQ5lOhjoTJ04EMjQFlF5C+/LLLyVfy+TJkzl27BgA48aNU7QfdVasWEH79u05dOhQvvVx4MABNmzYgJOTE1WrVs2yynD79m127dpFr169JJ2Qj9G4cWPWrl1LdHS0LHAuO2rXrs3jx49xdnZGEATWrFmjaC3RAjl9ULFq1Sopg9DV1VUR1d20tDTmz58PwIwZMyhcuDAvX74kKSmJAQMGSMNGJWTVVq5ciZmZGRcvXpT5EebPny9bc848JxUEgUuXLkmVpj/EH3/8gYGBAXfv3iUgIECK9hQEga5du1KyZEkePnxI3759FZFuU0f1xAsODub48ePS9pIlS+Li4pIrJa68sG7dOimPQiUfJ4oir1+/lubjjx494sSJE5w8eVKRkHiQOyITExPp168fv//+e57a2LVrF5aWlsTFxdGkSRNZmwkJCRgaGlK0aFEKFy7MsWPHMDMzQxRFDh8+/MHcC00osEahZMmSGBoakpaWxvr16/NlyVL9S1KlSpUcn7batC8IgszA2NjY4O3tLRmBN2/eyLQyDQ0NMTU1zfUc1dXVFVdXVzp06ICPj48sevTp06eMGDGC/fv354uSkkqgR30VKDY2FltbWxwdHRUzCvr6+tSvX1/yzQAULVqUmzdvYmJigpWVlRT3IQgCEydOpG/fvgQFBSnSvzqHDh3Ks0GAjAfct99+i4WFBX5+fsybN0+6lkOHDmFlZUXdunVlD4dJkyYREBCg+JSzwBoF1dN8/Pjx/4igibe3t+Lpy6VLl0YURbp3786dO3fo3r07devW5fDhw+zcuZOrV69y+/Zt2XJdyZIlqV69On/++Weu+jh8+LAUi2BlZSXLu797965WeSIfolatWpLwqkqyrnTp0tja2vL27dsPJhHlBQMDA3bv3i15/O/cucNPP/3EqlWrSEpKonDhwpiamlKtWjUaNMioWert7Z3r6VduGDJkCJChqK2pWNCcOXM4efIkK1aswM7OTubryRxRu3nzZvz8/GSjCSUpkEahXr16DB48mNOnT+eLtc+OwYMHS1Y6KCiI69eva92mKhnJ3t6eDRs2kJSURFhYGG5ubjn+zfPnzzVOkX3y5EkWzc38wt3dXYr8bN26NXXr1qVfv36kpaUxc+ZMxZZd/f39ad++Pa9fv8bT05OdO3fKRiZv3rzh2bNn3L59WzJEoaGhREdHK9L/wIEDmTdvHpAhDqzpKkBqaionTpygRo0a1KxZU1qxcXFxkZZ0nz59yr59+xSNSciOAmUU2rVrx+7duzE0NCQ5OZkWLVr8I/2qZ6Bt2rSJwYMHK9JufhT/+FT47rvvePToEbNmzWLAgAEEBQUxadIkNmzYoGg/HxLMzYkLFy4o1v/169e5cuUKKSkpnDx5UpE2//hDqmeU7wYgOwqUUZg8eTKGhoakp6fL8tnzm6JFi0q/q8/vdXyYtWvXZqve/V8iIiJC8aXcf5sCZRRUYaW9evVSVJH5Y6gbAvW6ADp0/BcpUEahbt26/0q/KSkp/ylZMB06PoRONk6Hjv8RdLJxOnTo0IhPavrwX5Xr0vWl6+tT6Cu36EYKOnTokKEzCjp06JChMwo6dBQAbGxsGD9+POHh4Tg5OeHk5JRvlaILvFGoVq0a9erVk15FihTJ9z6PHz8upVCLoijLANQECwsL2rdvz+LFi6WXKIqkpaWxadMmunfvrtCZZ+XZs2e8ePFCylPILwICAkhLS+PXX3/l119/5cGDBzx9+pTt27dTo0aNfO37v4AqtH3nzp3Y2toyfvx4Fi1axM8//yxVhFKKT8rRmFu6devGlClTgAyjYGxsjCiKCIJATEwMPXr0yJdkkRYtWmRrAFTbW7Zsmec2a9asyYEDB7Ik6KSnpyOKIn369KFq1aqKFCDNjL6+Pnp6eowdO5YrV64o3r6K1q1bM3DgQJYtWybVUqhRowZLliyhe/fuNGnShJYtW8pK92vK7t27cXZ2pkaNGvmW51GsWDEWL14shbsLgsD333/P5s2buXv3br4VynFycsLDI6NGslKqU9lRoEYK5ubmzJ49mx07dlC/fn3q16+PsbGxrCJS9erVOXXqFPXqKatte/z48Q+OCDTNw+jdu/dHM/bUM+aUomjRopw+fZoVK1ZIVZbzCzMzMwwNDQkJCZG2Xb16la+++or9+/djZWUlFcvRhqJFi1KxYkWKFy9O27ZtZfuMjIzw8fEhNjZWK9UmIyMjDh8+LKVpi6JIeno63t7eXLp0iXPnzhEYGEjlypWpWLGiYkN89TqX+U2BMgpTp05lypQpsqF7dq9SpUqxaNEiqlWrpki/LVq0yLfkq9zUgcwPo7B8+XKcnJxYvnw5kPGFmjVrFsOHD8fBwQEHBwcmTZqkiJLxjh07OHfuHKdOnZJtv3fvHl27duXRo0eKVGOaMGECNWrUQF9fP8sXv1mzZowbNw4bGxuNDZCRkRGLFy+mYcOGOR5TuXJlhgwZwrVr17hx4waXL19mx44d7NixQ6M+1clPtSl1Csz0Ydy4cYwdO1bKWFTPXFT9fv78eczNzfnss89wdnbmp59+wtHRUeu+M48QVP0pEQ2am8ItSicV9e7dWxqGqmotFCtWjAoVKkjHpKamcubMGSwtLbVKc1aVFvvQF0mJz9HExESqa5AZS0tL/P39pdJwH9NsyAkbGxs8PT1l2+7evUtycjKvXr3i888/z/I3pqamkr5FQaHAGIWqVatKN09mcVnVzzVr1jB06FBsbW0RRVHrkcKMGTNkgR8nTpyQ/Ab/VNp2fvDDDz+gp6fH48eP0dfXp2jRokRFRXHu3DkOHjxIXFwcL1++VERgZPbs2ZQtWxYXFxfevn2bZf/w4cOxsLCQRiyaUqZMGelJmpiYKFPp/vzzz6lZsyaQYeCVqt94/vx53N3deffuHUlJSVSqVImvvvqKpk2bUrt2bc6dO8ezZ8+yFDbi2GcAACAASURBVEnRlEaNGinSzscoMEZh2LBhkjMR5COFt2/fEhMTQ1JSkuRjgIynh6ZkNggtW7aUydBnNgqaRqYNGzaM1atXU6hQISIiIti3bx/Tpk2TlU27evUq+vr6eRK3yQ4DAwOCg4MpWbIkGzZsYMSIEaSnpyMIgtayezmxdu1aDh48yPbt23F1dZXtGzp0KIsWLcLQ0FCrSkwWFhYyf8Xs2bOlIr6FChVi/PjxALx69QoPDw+tP0cV9erVIzw8nLCwMAYPHszly5cZM2YMxYoVo3jx4rx48YKUlBRZtauCQIHwKXTr1u2DPoT+/fvj6OjIrl27suzThOPHj2cJDVU3CADNmzeXvZ8xY4ZGfQUFBWFqakqtWrXo0qULFy5cyCLsERwcTEBAgEbtq1OvXj06derE+/fvWbhwISkpKaSmpuabQYCMgiHR0dF06tRJ+nKWKVOGwMBAVq1aRXp6Ol27ds3ib8gLvr6+0rLmoUOHCAkJkVYAhg8fLhnwX375BT09zW/558+fy8RsIEOod+DAgcTExPDFF18A8Pr1ax4+fEhiYiLv37/nwYMHWtfAVBVbGT9+POPHj+fnn3+WpoBKUyCMQnBwML/++qu0wnD9+nWuX7/OiBEjaNGihfSUePv2LcuWLZOtRmjCx0YBx48flx2T2WBowrt379ixY0eOIq89evTQejqkSj2Pjo4mJiZGq7Zyy+PHj/Hy8iI5OZn+/fszcOBAbty4wZAhQzhx4gStW7fWapTQunVrWV3EWrVqUb58eZycnAgICJCpRDVu3FgrsdmEhARcXFyIiorKImZTpUoVNm/ezKhRozRu/0Oopg7+/v6ULVsWJycntm/fTmxsrOJ9fbLTh3r16nHw4EEsLCykaYPqyV+tWjUEQWDVqlXExMTI5o/u7u7S7ypJ9dySecoA8mlKdnEKM2fO1HiUoKJOnTqEh4dLHvMZM2awdOlSqVz5999/j6mpKVevXsXKykrjG3v16tW8fv2aadOmkZ6ezrNnz2jSpAmvXr3SSu/wY/zyyy/cvXuX2rVr880339CgQQNFalxaW1vj6+srK6hbtmxZDh48CGT4FlRTyKCgIEXqef7111/ZBgtFRETg6OjIsmXLEEWRlStXat2XjY0NNjY2REREsGjRIiIjI6X4BFVZ99jYWKlCtlJ8siOFxYsXU6pUqY8uP1atWpWhQ4fy1VdfMXToUNnf5JXMBiHzCCG7bDNtDQJkSIuNHj2aY8eO4ePjw6xZs3j16hVLly5l6dKlMhGazPPyvCCKIlu2bKFevXr4+/tjbGzM9evXuXz5MsHBwVqL5GbG1NSUGTNmkJaWhqmpKQcOHEBPT0+xoKJHjx7Rv39/bt++ze+//84vv/zC9u3bqVevHnp6elIV6QcPHjB06NAsWoxKsnjxYul3FxcXjUepKmxsbLh//770ZZ8wYUK2AUsRERGKxzB8siMFCwsL2RQgp58f2pcXR+PHQpUzGxn1lQglWLduHevWrfvocZ999pnWfSUlJeHt7Y23tzehoaHY2Njg5uaGo6MjtWrVypWi0ccoU6YMx48fx8rKijNnzuDu7s6bN294/fo1lStX5uxZZQTJHzx4QIMGDUhMTMwypO/QoQOQEZOhlPBLTqjHELi6uqKvr6+VXmbjxo2BD0cu2tjY0LNnT0WU0dT5ZEcKqiVI9VdMTAyrV68mMDCQ8ePH079/f+bOnUtwcHCOo4jckt0S4/Tp06U8h8wonQdfsmRJLC0tJQEVFYULF5YZQCWGpeq4urrSt29fjhw5QtmyZaWnq7aEhIRQuXJlgoODadq0KXFxcdKSZH7oPGb3BSxevDipqalcvnxZkViID6G+onH+/HlF1Mogw4eQHTY2NlLAV0REhCJ9qfhkjYKfn580UoiPj2fp0qU0b96cESNGMGLECJYsWcKWLVtYunRptoKweXU0ZucsPHHiRLbGIvPypLaMHj2a69ev8+DBA+rUqSPbN3HiRCnJKzw8XNKKUJKYmBgWLFgAZDgjVVJ82lCmTBkeP36cJWLzwoULdOrUSfEw9Jw4evSoFKClLYIg5JhwN3z4cOn3U6dOaW0UVCOE8ePH4+/vL/NjODk5ER4eLq3mqH4qxSdrFNSf/jExMQQGBmar7jt16lTc3NykY4ODg1m9ejVxcXF5ejq0bNmSmTNnfvTLnptj8kLJkiWZNm0aZmZmvH//XrbeDtCzZ0/p97CwsHwvMW9gYKBIgVxBEChbtixLly6VGZn58+cjCMI/FvyllMaDnp4eEydO5M6dO9kKwH5MFFYTVCsO48eP58yZM4SHh/Pzzz9z5swZyY+waNEixSX/Plmfwvnz5zl69Cjt27fH2dmZmJgYdu3axe7duwFwdnZm6NChsoCmpUuXMmfOHGlkkVkN+GOonIaqJcfMy45KGwTICK5RxSUYGRlx5MgRKlWqhKGhIV5eXtIy5KNHj/jxxx+17q9mzZqcOnWKvXv3MnbsWF68eIGFhQVjx44FMoROIyMjte5nzZo1zJgxgyFDhlChQgUSEhJ48+YNd+7c0SqGJLf07NmTJ0+eEBgYqEh7pqamkhKUjY2NlNFZp04dJk2aJDtWXeZPGyIiIpgwYYI0hVCPaLx//z4eHh6KTx3gEzYKAP369WP16tW4ubmRnp6Om5sb7u7usiVKURR59uwZ/fv358iRI9LfXrt2TdH0aSWdiurExcUxaNAgAgMDKVKkCJ999hmBgYE4OTlhb28vHXfx4kVFngh37twhIiKC/v3707p1ay5fvkydOnUoU6YMsbGxeHt7a90HwKxZs0hJSWHEiBHSZ6e+rKxEmvSHqFKlCtHR0Yqt479+/ZpDhw7Rvn175s2bx6JFi+jevTtubm6y8v9hYWGylQhtWbRokWyq0LNnTxo1aqRIQFROfNJGIT4+nh49etCtWzfMzc1xd3fHzs6OqlWr8vbtW06fPs2cOXMUidFXRyX/1aJFi3wZHWRm27ZtCILA2rVrMTQ0ZPDgwbIv0LRp03K1MpEbkpKS8PDwYOfOnbRt21ZK296xYwfe3t6K3mjff/89a9aswcHBgY4dO/J/7Z13WBTX+sc/B5CiFDUYFUsCsSBYo16NDewtRuxEYyWIuTFqTOxE0dhzjUbEhhoLtiioWBLF/DQqaiyJRoxGsUsQFAsSSwTm98eyc3eR5u4MJXc+z7OPMDPMOazLO+e85z3fb/369Tl9+jQrVqxQpE4hO4oXL07v3r0Vs3ED3QaxuLg4ABo0aMCGDRteuiY+Pp7IyEj+/vtvxdoF3YhBrerFrMg1KAghVgHvAomSJNXMOBYE+AP6KpqJkiTtyTg3AfAD0oARkiSZneXRu0EpNRTMDSVqD16VDRs2ZPlBU4OUlBTFNunkxv3793PVolCaJ0+esH37dsXNh4cOHcrcuXPx8/Nj8ODBlClTBtAVSbm4uJCSkqJoewVFXhKNq4GsPkHzJUmqm/HSBwQPwBfwzPiZxUIIzVpJI9+ZMmWKKqXcsbGxTJgwgXLlymFpaYmlpSWOjo7/mIAAeQgKkiQdAvK6DtYV2CRJ0nNJkq4BscDLm8w1NDQKLXmyjRNCvAnsyjR9GAQkA6eAzyRJeiCEWAQclyQpLOO6lcD3kiRtzen+mm2chob6qG0btwR4C6gLxANZl13lgBBiqBDilBDilNolqBoaGnnHpNUHSZLkLXVCiFBAXxsbBxjuzqiYcSyreywHloNupAD/XLsurS2trcLQVl4xaaQghChv8G03ICbj60jAVwhhI4RwBaoCJ0xpQ0NDo2DIy5LkRsAbcBZC3AamAN5CiLqABFwHAgAkSTovhPgO+B1IBT6WJEkZ7SsNDY18IdegIEnS+1kczraSRpKkGcAMczqlUXAEBQXh5eXFTz/9VCD1GhoFT6HdEJUVb7zxBosXLyY1NVV+bdq0Kd923P1TCQoKkkvGvby8ZDUp/UtJmjZtykcffURUVBTp6emkp6czceJERdvQMI9CXeacmaNHj76kjNuqVSvee+89VQxTsqNLly5MmzYNNzc33nrrLe7du5dvbSuNt7e3nIjKLByjdEAoX768LJ33/Plz0tPTsbCwIDAwkP79+7Ns2TIWLFigaJv/Kzg7O3Pw4EGio6Nf8qZ4VYpMUPD396dcuXLyfoBffvlFrm3PbJayZMkSOnfuTGhoKF9++aUi7Ts4ODB8+HB69eplpHlQq1Yt1Up4K1SowJgxY+jTpw9ly5YlLCyMAQMGKNqGvu9KK0kZYm1tTUBAAJ988gmg2ww1adIkSpYsyfLly7G1taV69ep4eXmZFBS6d+/O1q1bjTbKJSUlMXr0aH7//Xf5utdffx0fHx8CAwPNCuSOjo5YWloyevRoihUrluO1vXv3xtXVFU9PT6O+mIKVlRWtWrUy2vinx8PDgxo1auDs7Mzw4cPNUuguMkHh008/xcLCgvT0dLZs2YKvr2+W1/Xu3ZuhQ4cihCAoKEixoPDTTz9Rt25dnjx5kq34Sl5p1KgRw4cP57PPPstym+2gQYPo1q0b7u7uVKlSBdDJwamh3KtHrYAAOsXlb775BoCZM2cyc+ZMnjx5wvDhw+VrUlNTTd54ZqinAcjWgWvWrDEKFPp/nzx5YrIwiYuLC/v3738lVa/09HRGjhxp1hPc2tqasLAwevToYbQrE3SjBL0FwJMnT8zell5kgkJoaCj/+c9/5P9wa2vrl3aj2dvb07t3b/lNefz4sdntOjk5MW7cOKpWrUpAQADr16/H2dmZ69evc+/ePZP8CkJDQ/H09OSvv/6StQqdnZ3x8PCgbt26sofBs2fPmD59Ol27dqVWrVpcu3bN7N/HEP0oQc2AABjZ3AcGBgLg5+cn6wQkJibSv39/oqKiTLr/gAEDCA0NpUaNGkbH/f39cXZ25o033gB4JSWurGjevDlbt27F2dlZPnbz5k0SEhKIi4ujQYMGxMfHY2FhQf369Y1+Vq8DYiozZsygR48eWZ5r3bq1rLuxfft2s7QhoQgFBUNatmzJiBEj+M9//iMfs7OzY/Xq1fj4+MjH9LsrzeGzzz5j/PjxNGnShOPHj+Ps7My6desAOHnypEluQ8HBwcyZMwd/f38jzwLQbRdfuXIlu3btIioqikqVKuHn58eLFy84efKk2b+PHr2IzMGDB1XfGq7/Y7x58yZVqlRhyZIlshZlYmIizZs3N1tf4fDhw0ZS/6DbVfvaa6/RoEED1qxZI9sFmPK5aNq06UsBYe/evQwdOlTebl6pUiVu3bpF165d5Tbu379PWFhYnqeYZcuWfUluv0aNGui3AWR+0Lm6usr6DdevX2fOnDmv/LtlpsgEBf1IQU9gYKDR97GxsUZJyN27d+fJ0TknunTpwsSJE0lJSeH48ePY2dkxePBgmjdvzqNHj0w2/ggNDeX48eN4enpSvXp1bt68KYua7ty5U3Y3cnR0ZO/evZQvX57w8HB+++03s34fPYbJRUNvTG9vb1WWIfX9rly5MqdOnZKVpjZv3syoUaNU9ZxISkri7t27sjr4tm3bTNLfGDhwoFFACAoKYvr06UZD9Vu3btG/f3++/fZb+dimTZteyVE783thZ2dHeHg49vb2AHTu3Fk+Z21tTVBQkPy537VrlyLvZZEJCikpKXTt2pVNmzZhZ2eHg4MDy5YtY/PmzWzbtg0HBwf5P2j37t1m+SMAVKlShdWrV2NhYcH48eOxs7Nj+PDhciSOjIzk+vXrJt//3LlznDt3LsdrBg0aJOv+K6WyDBhJzWU2wJkyZYoiBjeGnDjx36JWR0dHnj9/zpgxY1i2bJmqlnV6Jk2aJH82Zswwv4QmKCiImTNnZjl3N/R8GDFihNmO4cOGDZPzF1OnTiU6Olo+V6tWLT744ANAl3jXT83MpcgEBdD9YaxYsUJOUPn5+cn244bCrUpk6CtXrkypUqX49ddfCQsL48qVK5QrVw7Q6SVmHvYrjZWVFe+++y4AZ8+eZevWHDeavhKGowT9sFZfg+/l5SWfV2PUcOfOHUaOHMmWLVsUv3dWTJo0SZbwmzx5sslCrnpZ+mbNmrF8+fIsp40hISH07dsX0PlO/vTTT2YJ7daqVUu2vXvw4IGR3Lujo6PRexgSEqJIDg2KWFAAncRXq1atjPQL9Zw/f54RI0bw7Nkzs9t5/PgxaWlp1KtXjzt37vDhhx8SFhZGTEwMDRs2VFxyKzORkZHyvHvYsGGKmZkYPt0OHDiQZfJNkiSmTJmiSFBwcHAwUkCqW7euYsKmufH7779TvXp1njx5Qv369c2SgEtISCCn7f2GQeLGjRu4ubmZ3BboVK/11nCgU/1OTk7O9vqVK1fSsmVLBg4caFa7UMQqGp2cnDhw4ACenp6yffqDBw9kj4eaNWuyfPnyl5ZsTOHkyZN4e3vz6aef4unpScOGDQFdUFJbZr1SpUrUqVMHgNWrV3PmzBnF28huxUEfCJRIPjZs2JCYmBgjgxslPQ9zQ28oNHPmTFU1IQ29IHbs2EG7du3Mvmfjxo1z9DMRQpCcnExQUBC9evWiV69eOQatV6HIjBT8/f157733qFKlivy0GzVqFLt27WLPnj3UrFkTSZLo1KkT9erVU8SWLDo6Wp7DDRs2jBs3bhAeHm72fXOjYcOGlC9fnvT0dLZv367KqCS7P3ovLy8As0VP7e3tWbNmDZUqVSImJobIyEgmTpyomF1cbkyfPh0hBMuXL2fmzJmqtqUf4t+6dYvAwEBiY2PNvufAgQP597//jZOTE/v27ZOTz9bW1kRERJCQkEDLli1VCXZFIijoi19sbGzkgHDp0iWWLVsGQPv27WWlXYA+ffoo/uGzsbHh7NmzikxNcmPgwIFIksQvv/zCzp07VWsn89RB73cB5ucTZsyYgbu7Ozdu3KBdu3Y0atTIrPu9KhMmTCAiIkL1gGBraysH0n79+pldtajn2rVrWcrt79ixg+fPn9OzZ0/VRj+FfvpQvnx55s+fb+QytHv3biMbrS5duhj9jFKuQHqKFStGamqqImvAeUH/IcvsFqUkmdfNDQOCucIflStXZvDgwYDO9u7OnTtGS2lqUrx4ccLDwxFCsGHDBsXNVw2xsLAgPDycJk2acPXqVdW9LFxdXenQoQMLFy7k6NGjqrVT6EcK8fHxxMfHy3kE0M0V9RnyqlWr0rlzZ/nc2bNnFbccr1OnDn/88QfHjh1T9L7Z4eDgwKlTp1TZHKQv0fb29kaSJKZOnWq0JHnw4EGzRwkdOnTA3t6exMREwsPDsbKyeqnaUC26d+9O165dTS5SehXat28vS+VPmjRJ9QTqgAEDsLKyUmRZNScK/UgBYMiQIcTExMjLjlWqVGHkyJGMHDmSTp06GTlNz58/X3G57fbt2+erbwHogpu+oElJMpvjGgaEqVOnKlLy3KpVK0CXhbeysmLEiBE0bdrU7PvmhrOzMxMnTkQIoeoIAXQ5E8MlwZws45VixIgRgDLl+zlRJIJCXFwc7du3Z/fu3dle8/TpUwYPHqz4kLtGjRoEBgaaXYSSV3x9fUlISJDzJWrQsmVLoz9+fTBQqi5h48aNgC5heuDAAbnyVM0hL+hGCfoVh1epIjSFgQMHyqsOhvkstXjnnXfkSlC1KfTTBz137tyhW7dulCtXjmrVqvHFF1/QokULLl26xO7du5k3bx537txRvN1u3bpha2urWIlxTlhaWtKlSxf++usv2rdvz9mzZ83e3JIdBw8eNHuDUHacP3+eP//8ExcXF5o1a4YkSRw9elT1vELz5s2xsLAgMTFR1XzM66+/LlcSAopZ+uVE9+7dsbCwUD1xCkUoKICuQCQuLo64uLh8Gc67u7sTGBiY7TZtpencuTO+vr6Eh4cTHh6uWkBQm9jYWCpWrJivbS5btoy+ffvy119/MWzYMFXb2rt3L7Vr1yY9PZ33339f0WrT7IiJieHMmTOvrMxsCkVi+lBQXLx4keLFi7N58+Z8bXfq1KmqFtv8EzEsc1dzlADw888/A7o6lvwICABr1qyhfv36pKenq96WFhQKEZGRkVhaWnL+/PmC7kqRY9iwYVhZWSmuTJVdW5aWlmYJ7RRm8mQbpzaabZyGhvqobRunoaHxD6VQJRr/qXZdWltaW4WhrbyijRQ0NDSM0IKChoaGEVpQ0NDQMKJIBwV7e3v27NmDq6ur6m1duXKFtLQ00tLSSEpK4vXXX1e9zfxgwoQJTJgwgb1798o2bunp6Vy+fJmQkBCzPQT+SdjZ2dGrVy/q1KlDxYoVuXDhgpGFof6VlJTE5MmTC7q7JlOkgwLo9iaULl1a9XZcXV35888/efDgASVLllSttNXHxwcrq5zzv/b29uzbtw8HBwez2ho0aBDTp09n+vTptG3b1mhjmZubG926dSM4ONisNvKbfv368f3338sBXK+ZqARDhgxh48aN7Nmzh6ioKKpWrSqf+7//+z82b95MUlISTk5OBAQEGKk/m0OtWrWYNWsW7dq1Y9WqVXLgliSJS5cuceDAAZo3b65IW1DEg0KFChVkow+1sLS0ZO7cuaSkpLB06VIqVqzI06dPqVu3riqlvN27d89144ufnx81atSgVKlSJrczZMgQQkJCjI5t3LiRtWvXyq+lS5fKO/OU4uTJk0iSpPhWah8fH44ePcqKFSuMAtzw4cMpWbKkIm1ERERw69YtbG1tjQICwLx58+jXrx9DhgwBoFy5corIo7Vt25Zjx44xbtw4fvjhBwYNGiSf0+8Y9vLyYv/+/Vy+fJmaNWua3WahWpJ8VUqXLk1KSoqssqwGrVq14rPPPmP06NGy9ZkkSbi4uFC/fn3ZCEQJ2rdvT9euXWWF6uyumTZtGl5eXibbyH344YfMnz8fW1tb2blo4sSJXLp0SdXpQqVKlShZsiSSJFG3bl0uXLigyD39/PwYM2YMNjY2PHnyhNWrVzNt2jQ2btxIs2bN+Prrr+U/VnOIj4/H1dWVevXq8cknnxhVT+7duxfQbXnXY+iMZSoTJ06UzZN///13fvnlF6PzVlZW+Pr6UqxYMd566y3GjBljtnhrkQ4Kx44d4+rVq7Ro0SLHbdWmUqdOHdkNylCRWE/v3r3ZsWOHIm21adOGbdu2kZSUlK0XQoMGDdi2bRupqalGH75XoUyZMnz22WcUL16cJ0+eZGtFpiS2traEhobSrVs3+Zip/c9MWFiYrNXw888/ExMTw0cffQTodms2a9bMaORlb29Ps2bN+OGHH0xu89dff2XIkCHZBhohBBYWFmbvQnVzczMyM16zZg1fffWV0TUWFhYkJyfLoxIlrAWL9PTBzs6OEiVKqHZ/Hx8fypQpQ1pamqouRk5OToSEhGBra5utpkGDBg3kp/usWbNMfqI3a9aMatWqARgZi6jJ3LlzadiwIRUqVJCfekqMSEaMGCF7NiYnJ/P5558bmbj269cPQDZf9fDw4MiRI0RERNCzZ0+z288OSZLkOb85jB49GicnJwDWr18vj1QN8fLykgNCcnKykTeEqRTpoFC5cmXeeustVe7t5OQkW6cb6u8D/Pjjj/I1SsjJ79mzh6pVqzJnzpwsE5ilS5cmJCSEpk2bsm3bNmbNmmVSO46OjvLT7dmzZy89ddTAxsaG9u3b06dPHx49egToHKbNFcD18fFh1qxZ2Nra8ssvv8hzb0Ps7OyIiori2LFjvPfee6xatYqaNWuSnJysmrSeYY5r4cKFZt2rXr16PH78mJs3b/LFF1+8pOrt7e0tS84lJyfTrVs3RVSZivT0oUuXLgghFLVU0zN8+HBKlSrFtWvX5CmEnvPnz9OlSxdq165NiRIlcjTpyAkhBGPGjKFhw4Y8ffo0S/l4IQQhISE0aNBAVpcylfXr19OpUycAJk+eLAc3NfHx8aFq1apG+oVJSUlmD3MrVKiAjY0NoFP2Pn36tHzOwsKCTz75BEtLS27cuEHHjh1ZtmyZnHBcuXKlKmpJ1tbWjBs3DtBJpl29etWs+7Vo0QIrKyvS09NfmlKWLFmSrVu3yiOJcePGKaYxUqSDQvHixbly5YoqKrp6y7bo6GgePnyY5TWHDx82OSCAzi1J/9QfMGBAlrL0Y8aMoXfv3jx+/JjAwECz2qtXr578tdKK19lhaJSixxSD15wwFLgtWbIkISEh9OnTB4D69evz4YcfyudTU1NfStYpxdSpU+Wgu2LFCrOnnPpl1cw4ODiwY8cOSpcujSRJLFy4UFG5wFyDghCiErAWKAtIwHJJkr4RQpQGNgNvAteB3pIkPRC67Mo3QCfgCTBIkiRV/hf8/f3p0aOH4jJsGzZs4F//+hebNm3KMpOrxDJdkyZNjKzT169fz4YNG7h9+zZbt24lJiaGGTNmyEVSwcHBLFq0yKw29c5CAPv375eP7927l44dO5p17+wICwtj0KBB9OjRg6VLl5KQkKC4ktWmTZtkIRwHBwc5IIBufq93nb59+zZt27ZV5SEyadIkRo4cCcCZM2f4/PPPFW9Dz/3797G0tOTIkSO0bdtWcceyvIwUUoHPJEn6RQjhAJwWQkQBg4AfJUmaLYQYD4wHxgEdgaoZr0bAkox/FSchIcHI0Vhpshr+Va9eXS4uMkcMJTY2lv3799O2bVv5mBCCihUrMmrUKPl7fbJq0qRJuLu706tXL5PbPHr0KN27d3/peLt27UhOTqZHjx5ERUWZfP+sSE1NZejQoRw6dIiPPvqII0eOZPn0e1VCQkJ45513eP/993F1dWX8+PEvXePj40OpUqVka/j4+HhFAkL58uXl3MGlS5e4f/8+kyZNwtramsTERHmUqQb6aRHoak3UsDDMNdEoSVK8/kkvSdJj4AJQAegKrMm4bA3gk/F1V2CtpOM4UFIIUV7pjhcrVkyVZcgSJUpQu3btbM+PHTsWa2trbt26JX/YTCExMZGOHTvi7u5Oo0aN2LZtG3fv3uXu3bukpaXJxTc//fQTKXZ14wAAFPJJREFUc+bMoU6dOvTv39/k9kA3RenQoQNRUVHcu3ePDz74QJZ9s7e3Jzw8nMjISMVVgy9dukTNmjUZPny4og5bgwYNYurUqWzfvh1Jkrh//z6LFi1i3rx5ODk5sXPnTho1aiS/l+ZsU7a2tsbT05PNmzezZ88ejhw5wpEjR4iMjGTz5s1YW1vz/PlzgoODVREQBl2eSx/8IiIiuHHjhirtvFJOQQjxJlAP+BkoK0lSfMapO+imF6ALGIai+7czjsWjICNHjjRrrTk7SpQokW21naenp7zWvmrVKuLjzfuV0tPTuXTpEoBRvcDTp0+xtrZm79699OzZU/YRNJenT58SFRVFdHQ0S5Ys4eDBg9jb2zN69GiqV69OiRIl6Ny5M+XKlTMrd5EV9+7d48CBA4oUEelJTU1l2rRpWFtb89prr5Gamsrdu3eNrjF0DzPHk3Pq1KmyjZvhCM7QqWzq1KnMnTvX5DZyonnz5kyePFkunf7jjz+yrWcxlzwvSQoh7IFwYJQkSUafGEn3Dr3SoqwQYqgQ4pQQ4pQpNuuNGjVSZVnp/v378ny7evXqRue8vLxwcnLi+fPn7Nu3T/G29VhbW/Pw4UN8fHwUCwiGPHnyBBcXF7Zs2UJoaCht2rRh8eLF8vmPP/5Y8Tb1qOGg/ffffxMfH/9SQOjXr5+ckzl27BjHjx83uQ0vLy85J7NgwQIWLFhAQkKCkQv0sGHD8Pf3N+t3yYpRo0Zx4MABo70UQ4cO5e2331a8LchjUBBCFEMXENZLkqT34krQTwsy/tWvOcUBlQx+vGLGMSMkSVouSVIDSZIa6Ata8kqFChVkv0WlSU1NlUtWu3fvzpAhQ7CwsGDOnDlyYUifPn3M+oDlRIMGDXj48CHvvvuuqpb3wcHBuLu707VrV+Li4vjqq69kR6o+ffrIBU5Ko0Rtfl4pWbKknP+ZN28epjx89BhuFjt+/Djt2rWjVKlSRscrV67MokWLuHHjBnXq1FHkdyhXrhxff/01Fha6P9U7d+7w/fffs2jRItVWUXINChmrCSuBC5IkfW1wKhLQp+YHAjsMjg8QOhoDjwymGYowcuRIkpKSlLylEevWrePatWsIIQgNDSUyMlLOJTx48EDxZJwhet8HtX0rIyMjCQgIICgoiIiICOzs7OQhcZkyZVRxIypdurRZeZhXZfv27fLnRMk9Kps2bcLDwwNra2tOnDhBkyZNaNKkCbt378bS0pIKFSqwZ88eGjTIVSM1Vzw8PIxqKhYtWkTnzp1VlXHLy0ihKdAfaCWEOJPx6gTMBtoKIS4DbTK+B9gDXAVigVDg38p3G9auXavGbQG4e/cuffr0kdfyO3bsiCRJ/PrrrzRt2lQ1O3o3Nzc++OCDfNuLf/bsWWrXrk3Xrl25cOGCXIJ8+vRpRWroM+Po6EjZsmVzv1Ah4uLiFBttZa5V+fPPPxk7diy9evXixIkTnDhxgsGDBxMQEMDjx48pV64cW7duNTkwCCHo1asXq1atokKFCgAcP37caJqnFrkmGiVJOgJkt7OjdRbXS4B6k9J84vTp07Ru3ZpevXrh6elJcnIyixcvVnUPxNKlS3n48KFq2evMxMbGEhUVhZeXF7a2tgC8ePGC4OBgVUZi5gzfTSU5ORkXFxf69evHjRs3Xso75BU/Pz+GDRtGrVq1OHz4MKGhoS/le+7fv8/KlSu5fPkyo0aN4r333mP+/PkmaR3069fP6MGXnJzM+++/n20hnZIUyYrGsWPH5ks7jx49yjdj2cOHD3Pnzh2jXXH5gd5KPT9ITEyUy3LzC19fXzp37ky7du3o2LGjySPMO3fu5NmA99ChQxw6dMikdvToR1Q///wzgwcP5uLFi2bd71UokkHhn4iSyjka/yUmJoaYmBjmzJlT0F15JebNm6fIjkdTKNK7JDWKHvk18tIwHc02TkPjfwTNNk5DQ8MkClVO4Z9q16W1pbVVGNrKK9pIQUNDwwgtKGhoaBihBYX/cVxdXVmwYAFbt27l9OnTBAcH06FDB1nqrKjQqFEjli5diiRJRERE8OjRI/z9/VUTj/knUySDgrOzM3FxcVladulfR44ckZV+izKSJLFv3z7s7e0Vv7evry+XL19m0KBBJCUlMWrUKJYuXcqtW7fkDThqMm3aNFlyzMPDw+T7VKtWjaNHj+Lv709wcDA//vgjEydOZOnSpezatYu0tDROnTpFmTJlFOy9MYGBgTkG0rp16ypWmh8XF0daWhr3799X5H6ZKZJBoUmTJrnW0Ddu3JiZM2fmU4/UoU2bNrKbkr4MWSmmTJnC6tWrmTdvHuXKlSMgIIDDhw9z/vx5zp8/L++YVIt//etfsj5BdHS0WYpIly5dYsCAAYwaNYqRI0cSEhJCSEgIdnZ2bNmyBdDpU6o1atCLvWQnzuPt7U14eLjZNn+gE6UtX16nWaTW/1GRDArPnz8nMTGRL774gjfffBMHBwf51apVK9n/sHXrl7ZmvDLvvvsuv/32m+zf9/fff/P5559TuXJls80+csLCwoJJkyYBOg3Fe/fuKXbv7t27M3bsWKysrJg2bZpqG7xyYvTo0bJuxIABA8wWDFm/fj3BwcFGXgt///03CxculGXPFyxYQLt27cxqJzMeHh4sXryYdevWZWlw07RpU3744QfefPPNHJ2/XqU9PWrpQBbJoLB3715cXFyYNWsWt2/f5unTp/Lr0KFD7NmzB8CsjUU2NjasWLGCnTt3UrlyZa5fv86xY8eIiIigb9++XL9+nenTp6sWGMqUKUOLFi0AnXWYUrz22mvMmjULGxsbrl69qoqIS2707t0bb29vAM6dO8f169dVa+vo0aOyWYqTk5OibuFCCHx9fbGxseHixYsvKTuVLFmStWvXUqxYMV68eKHIcF8/Gjl58qRs+ac0RTIo5ISHh4esva+XOjOF2bNnM2TIENatW4eHhwdubm40bdoUX19f2eRzwoQJZvv2ZUf//v0RQhAfH6/ok7xv376ygU5++D5kxtPTk2+//ZYyZcqwefNmVUVO9ehFc5SkWbNm7Nq1Sx7NzZ492+h869at2b59O2+++ab8vbl4e3vLBjPz58/Xpg95wdbWlhkzZshPIVPr7Pv06cPHH3/MvXv3CAgI4M8//zQ6r1dUDg0NVUU8FpCNWB8/fkxqaqoi93RwcJBdryIjI2XPxfxk8uTJcn5k165dpKSk5HsfzMXa2prevXvLO0wzq1NbWFgwYMAAo01unp6eZrfr4uIiO43HxMSYfb/sKFQVja+KobRXx44dGT16NGXKlOH58+fMnTuXyMhIk+5bo0YNrKysmDhxYpbRuFmzZoDuSWvq/vycsLKywsdHJ469Z88exXQI7OzscHNzA8yTpzcHvYdjeHi4Yua8+YWjoyM9e/ZkzJgxslzd6dOnjbbyu7m5sXDhQqOkZlJSEkePHlWsH5cvX5Yt+NSgSAaFsmXLEhAQkKVCUVpaGqGhoYqUj2b1hHZ2dsbLy4sHDx4YDb+7dOnCzp07zW4TdMlNvaL01q1bFbknYGQ5Z5gUa9GihZF71J07d2RzFSXRe1w8ePCAqVOn5ls+Qym9xEqVKvH111/LqwjPnj3j3XffJTExkdKlS9O2bVu+/PLLl/xNZ8+erciTXe9pun//fkXl5TJTJINCdgEBdEFh+fLlirTTpk0bNm/eLI8W7Ozs+O677wC4cOGCkTqRkpF7yZIlCCGIjo5W9Anj6OgoJ0bPnDmDv78/ffv2xdvbm/T0dKNrg4KCaN269UtTJ1MpXbq0LGPfqVOnfBupWFtbv2QQbArly5dnw4YNRsuKlpaWssv1qFGjZK9K0K183L17l549exr5XJqDfpSnNkUyKEybNo3o6GhZU9BQcv3EiRP89ttv+Pn5sWbNmpxuky1BQUF88803zJ07l5s3b/Laa68Znf/2229fWl4yV2nHkNdff51bt24pvq7+ySefyEt2Fy9e5MGDB0yZMoXhw4eTkpIim4t06tSJyMhIFi1alKWj1Kvi5ubGgQMHqFixIm+99ZYqqw3VqlWTk3qG1K9fX05mLl26lLCwMJPu/+uvv75U/FSsWLEs1ZgCAwNZsGCBoonAN954Q1amVlOCH4poUIDsM+fXrl3j7bffxtXV1az7P3jwAH9/fz7//HN8fHzYvn07169fRwiBmtoP+n7PmjVL1SRcZGQkffv2zXJlw93dHVAmY25jY0NUVBQVK1YkLS1N0YBga2uLt7c3gwcPpnbt2rnK0i9btszktoQQPHr0SJaTe/jwIcWKFaNEiRLyNWfPnqV9+/Y8fPhQcaOWgIAAXtUKwVSKbFDIjJWVFY0bN6Zt27Y8fvyYgwcPKnLfR48esWbNGtq2bYuTkxP79u1TxAsxK6ytrZkzZw4pKSmKL6N5enpSrFgxQLd2//7772epdGxlZSUX+CjhwFWrVi35CR4aGmr2/fSUKVOGiIgImjRpkuef+eabb2jZsqVJ7ZUtW5by5cvLKwoHDhzAxcVF9l5ISUkhKChIlcRzflNolySrVatGo0aNcHFxyfXaihUr0r59ezZv3oyjoyPr1q1TLCjo0S/lqSkt7+rqSo8ePVi+fLniEuvnz5+Xn143b97MVvp8/vz5tGnThhs3bhAYGGhWm56ennJwe/r0qaLFNt999x1NmjQhISGBQ4cOMWLECNzd3XF3d6djx45yFaMhDRs25Ny5cy8lAvNKfHw83333Hd999x0NGzY0kltv0KCByatdr8qMGTNUvX+hDQpHjx4lOjo61+Ig/RLQjh07KFu2LKmpqUREROT4M6agX4bcsGFDluf1T2FT0S+BAqqJjJ48eRKAbt26UbFiRfm4tbU11atX5/z58wwbNoz09HS++eYbsx2aDZNvH330kaImOi1atODFixf06dOHli1bEhISwpUrV+jZsydfffUVDg4OPHv2jE8//ZSBAwcSFhaGEILq1auzc+dOs6aXZcuWZfHixUY+kmrY2xuSnJwsJ4MNE5pqUGinD/pffOTIkXLGfPny5Tx9+hRXV1cqV67MypUrsbOzk3cQPnv2DD8/P8VHCfr+nDt3LtvzgwcPNmvVw8PDgw8++ABA0X0OhkyfPh0vLy9sbGyIjY1l7dq1JCcn065dO7mmPiEhga+//lqunDOVbt264evrC+jW6aOjo83uf2Z27NhBdHQ0PXr0wMXFhTp16hgtuxrO98PCwhg4cCBr166lX79+/Pjjj8yfP1/eJ5NXhBCMHz+eSpV0zogpKSlMmDBBmV8oB2bPns24ceNUce7KTKENCnqcnZ2ZNm0agPxvVty6dYsuXbqoUullZ2cHkOMH6MqVK2a14eLiouoGK4AjR44wb948/Pz8KFmypOwArXdRPnjwIAEBAWb/LgDt2rWjePHiSJLE8uXLuXr1qtn3zEzPnj1xd3fH1dVVDgAXL15k3bp17Nq1K8ufmThxolnO0HPnzmXEiBGAro6lX79+2balNCdPnqR169ZUq1YNJycn1QqYCn1QyI2bN29y7NgxPv30UxITE3P/ARPo0aMHaWlpco1CVpi7j6BVq1ZIkqTYmnZWvHjxgnHjxhEfH0+PHj145513AN1y6s6dOwkODlY8a/706VOzcxNZ8eWXX/LFF1/IVa1btmwhKiqK77//Psfaitu3b5tc+NO4cWM+/PBDQOd2NWPGjHwLCACrV6+mdevWtG7dmp07dzJ69GhOnTqleDuFNih4e3vLa+QdOnTIcrlp7NixrFmzRlWzWSEEH3/8MXv37iU5OVm1dgDS09PZuHGjqm0AspW6muinWuvWrVPl/kFBQXl2bFKyTf3wfcSIEflqlgu6GpyPP/6YJUuWIEmSKgEBCnFQOHLkCEeOHAF0e+8LisuXL+Pm5mZ2IjE3xo4dm292ePnB4sWL88UMNT/JT4u9rIiNjSU2NpalS5eq2k6hXX0oLBQvXpxz586pVpugoVHYKLQjhcJCXuokNDT+SWi2cRoa/yNotnEaGhomUShGCkKIu8BfgDpVO+rhjNZntSlq/YXC2+c3JEnKVee+UAQFACHEqbwMbQoTWp/Vp6j1F4pmnw3Rpg8aGhpGaEFBQ0PDiMIUFJTRUMtftD6rT1HrLxTNPssUmpyChoZG4aAwjRQ0NDQKAQUeFIQQHYQQfwghYoUQ4wu6P9khhLguhDgnhDgjhDiVcay0ECJKCHE5499SBdzHVUKIRCFEjMGxLPsodCzMeN9/E0K8XYj6HCSEiMt4r88IIToZnJuQ0ec/hBDtC6jPlYQQB4QQvwshzgshRmYcL9TvdZ6RJKnAXoAlcAVwA6yBs4BHQfYph75eB5wzHZsLjM/4ejwwp4D72AJ4G4jJrY9AJ+B7QACNgZ8LUZ+DgM+zuNYj4zNiA7hmfHYsC6DP5YG3M752AC5l9K1Qv9d5fRX0SOFfQKwkSVclSfob2AR0LeA+vQpdAb2O/BrApwD7giRJh4DMLqbZ9bErsFbScRwoKYQonz89/S/Z9Dk7ugKbJEl6LknSNSAW3WcoX5EkKV6SpF8yvn4MXAAqUMjf67xS0EGhAnDL4PvbGccKIxKwTwhxWgih36hRVpKk+Iyv7wBlC6ZrOZJdHwv7ez88Y6i9ymBaVuj6LIR4E6gH/EzRfa+NKOigUJRoJknS20BH4GMhRAvDk5JunFiol3KKQh8zWAK8BdQF4oF5BdudrBFC2APhwChJkowUeIrQe/0SBR0U4oBKBt9XzDhW6JAkKS7j30RgG7pha4J+GJjxrzp6cOaRXR8L7XsvSVKCJElpkiSlA6H8d4pQaPoshCiGLiCslyRJLx9e5N7rrCjooHASqCqEcBVCWAO+QP6I578CQogSQggH/ddAOyAGXV/1GvQDgcJoo5xdHyOBARmZ8cbAI4Ohb4GSab7dDd17Dbo++wohbIQQrkBV4EQB9E8AK4ELkiR9bXCqyL3XWVLQmU50mdlL6DLJkwq6P9n00Q1d1vsscF7fT+A14EfgMrAfKF3A/dyIbrj9At281S+7PqLLhIdkvO/ngAaFqM/rMvr0G7o/qPIG10/K6PMfQMcC6nMzdFOD34AzGa9Ohf29zutLq2jU0NAwoqCnDxoaGoUMLShoaGgYoQUFDQ0NI7SgoKGhYYQWFDQ0NIzQgoKGhoYRWlDQ0NAwQgsKGhoaRvw/Po9+U5vTjywAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "images, labels = next(iter(data_loader_train))\n",
    "\n",
    "img = torchvision.utils.make_grid(images)   # (batch_size,channel,height,weight) ->> (channel,height,weight)\n",
    "img = img.numpy().transpose(1,2,0)  # 完成数据类型的转换和数据维度的交换  (channel,height,weight) ->> (height, weight, channel)\n",
    "\n",
    "std = [0.5,0.5,0.5]\n",
    "mean = [0.5,0.5,0.5]\n",
    "img = img*std+mean\n",
    "print([labels[i] for i in range(64)])\n",
    "plt.imshow(img)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Model(torch.nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Model, self).__init__()\n",
    "        self.conv1=torch.nn.Sequential(\n",
    "            torch.nn.Conv2d(1,64,kernel_size=3,stride=1,padding=1),  # 卷积层\n",
    "            torch.nn.ReLU(), \n",
    "            torch.nn.Conv2d(64,128,kernel_size=3,stride=1,padding=1),\n",
    "            torch.nn.ReLU(),\n",
    "            torch.nn.MaxPool2d(stride=2,kernel_size=2))\n",
    "        self.dense=torch.nn.Sequential(\n",
    "            torch.nn.Linear(14*14*128,1024),\n",
    "            torch.nn.ReLU(),\n",
    "            torch.nn.Dropout(p=0.5),\n",
    "            torch.nn.Linear(1024, 10))\n",
    "        \n",
    "    def forward(self, x):\n",
    "        x = self.conv1(x)   # 卷积处理\n",
    "        x = x.view(-1, 14*14*128)  # 对参数实现扁平化，make sure维度匹配\n",
    "        x = self.dense(x) # 全连接层进行分类\n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model(\n",
      "  (conv1): Sequential(\n",
      "    (0): Conv2d(1, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (1): ReLU()\n",
      "    (2): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (3): ReLU()\n",
      "    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "  )\n",
      "  (dense): Sequential(\n",
      "    (0): Linear(in_features=25088, out_features=1024, bias=True)\n",
      "    (1): ReLU()\n",
      "    (2): Dropout(p=0.5)\n",
      "    (3): Linear(in_features=1024, out_features=10, bias=True)\n",
      "  )\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "model = Model()\n",
    "cost = torch.nn.CrossEntropyLoss()\n",
    "optimizer = torch.optim.Adam(model.parameters())\n",
    "\n",
    "print(model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_epochs = 5\n",
    "\n",
    "for epoch in range(n_epochs):\n",
    "    running_loss = 0.0\n",
    "    running_correct = 0\n",
    "    print(\"Epoch {}/{}\".format(epoch, n_epochs))\n",
    "    print(\"-\"*10)\n",
    "    \n",
    "    for data in data_loader_train:\n",
    "        print('111')\n",
    "        X_train, y_train = data\n",
    "        X_train, y_train = Variable(X_train), Variable(y_train)\n",
    "        outputs = model(X_train)\n",
    "        _,pred = torch.max(outputs.data, 1)\n",
    "        optimizer.zero_grad()\n",
    "        loss = cost(outputs, y_train)\n",
    "        \n",
    "        loss.backward()\n",
    "        optimizer.step()     \n",
    "        running_loss += loss.data[0]\n",
    "        running_correct += torch.sum(pred == y_train.data)\n",
    "\n",
    "    testing_correct = 0    \n",
    "    for data in data_loader_test:\n",
    "        X_test, y_test = data\n",
    "        X_test, y_test = Variable(X_test), Variable(y_test)\n",
    "        outputs = model(X_test)\n",
    "        _, pred = torch.max(outputs.data, 1)\n",
    "        testing_correct += torch.sum(pred == y_test.data)\n",
    "    print(\"Loss is:{:.4f}, Train Accuracy is:{:.4f}%, Test Accuracy is:{:.4f}\".format(\n",
    "        running_loss/len(data_train),100*running_correct/len(data_train),100*testing_correct/len(data_test)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "选取测试集数据评估模型的准确率\n",
    "'''\n",
    "data_loader_test = torch.utils.data.DataLoader(dataset=data_test,\n",
    "                                               batch_size = 4,\n",
    "                                               shuffle = True)\n",
    "\n",
    "X_test, y_test = next(iter(data_loader_test))\n",
    "inputs = Variable(X_test)\n",
    "pred = model(inputs)\n",
    "_,pred = torch.max(pred, 1)\n",
    "print(\"Predict Label is:\", [ i for i in pred.data])\n",
    "print(\"Real Label is:\",[i for i in y_test])\n",
    "\n",
    "img = torchvision.utils.make_grid(X_test)\n",
    "img = img.numpy().transpose(1,2,0)\n",
    "\n",
    "std = [0.5,0.5,0.5]\n",
    "mean = [0.5,0.5,0.5]\n",
    "img = img*std+mean\n",
    "plt.imshow(img)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
